Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Added
- `dm system ai-engine show` and `dm system ai-engine set <URL>` — view and
configure the AI Engine URL.

## v1.2.0

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ dm system upload-licence ./licence.lic # Upload a licence file
dm system logs -o logs.tar.gz # Download application logs
dm system admin-install --email admin@co.com # Initial admin setup
dm system set-locality AU # Set system locality
dm system ai-engine show # Show the configured AI Engine URL
dm system ai-engine set <URL> # Point DataMasque at an AI Engine
```

## JSON output
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: datamasque-cli
description: Use when the user wants to interact with a DataMasque instance — start masking runs, check run status, list connections or rulesets, manage seeds, manage ruleset libraries, check system health, or any task involving the DataMasque API. Triggers on "mask the data", "start a run", "check the run", "list connections", "list rulesets", "upload a seed", "check DataMasque health", "dm status", "ruleset library", or any request to operate DataMasque programmatically.
description: Use when the user wants to interact with a DataMasque instance — start masking runs, check run status, list connections or rulesets, manage seeds, manage ruleset libraries, check system health, configure the AI Engine, or any task involving the DataMasque API. Triggers on "mask the data", "start a run", "check the run", "list connections", "list rulesets", "upload a seed", "check DataMasque health", "dm status", "ruleset library", "configure the AI Engine", "set the AI Engine URL", or any request to operate DataMasque programmatically.
argument-hint: e.g. "start a run with docx_masking on var_input_docx"
user-invocable: true
---
Expand Down
33 changes: 32 additions & 1 deletion src/datamasque_cli/commands/system.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""System-level commands: health, licence, logs, admin-install."""
"""System administration commands."""

from __future__ import annotations

Expand Down Expand Up @@ -116,3 +116,34 @@ def set_locality(
client = get_client(profile)
client.set_locality(locality)
print_success(f"Locality set to '{locality}'.")


ai_engine_app = typer.Typer(help="Configure the AI Engine.", no_args_is_help=True)
app.add_typer(ai_engine_app, name="ai-engine")


@ai_engine_app.command("show")
def ai_engine_show(
profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"),
is_json: bool = typer.Option(False, "--json", help="Output as JSON"),
) -> None:
"""Show the configured AI Engine URL."""
client = get_client(profile)
response = client.make_request("GET", "/api/settings/")
url = response.json().get("dm_ai_engine_url") or None
if should_emit_json(is_json):
print_json({"dm_ai_engine_url": url})
return
# An empty table cell would look like a rendering bug.
render_output({"dm_ai_engine_url": url or "<not configured>"}, is_json=False, title="AI Engine")


@ai_engine_app.command("set")
def ai_engine_set(
url: str = typer.Argument(help="AI Engine base URL"),
profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"),
) -> None:
"""Point DataMasque at an AI Engine."""
client = get_client(profile)
client.make_request("PATCH", "/api/settings/", data={"dm_ai_engine_url": url})
print_success(f"AI Engine URL set to '{url}'.")
44 changes: 44 additions & 0 deletions tests/commands/test_system.py
Comment thread
elliotsimpson-dm marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch

import pytest
from datamasque.client.models.license import LicenseInfo, SwitchableLicenseMetadata
from typer.testing import CliRunner

Expand Down Expand Up @@ -36,3 +37,46 @@ def test_licence_projects_to_user_facing_fields(mock_get_client: MagicMock, runn
assert '"platform_name": "DataMasque"' in result.stdout
assert "switchable_license_metadata" not in result.stdout
assert "license_source" not in result.stdout


@pytest.mark.parametrize(
("extra_args", "settings_url", "expected_output"),
[
(["--json"], "http://engine.example.com:9021", '"dm_ai_engine_url": "http://engine.example.com:9021"'),
([], "http://engine.example.com:9021", "http://engine.example.com:9021"),
([], None, "<not configured>"),
([], "", "<not configured>"),
],
)
@patch(f"{MODULE}.get_client")
def test_ai_engine_show(
mock_get_client: MagicMock,
runner: CliRunner,
extra_args: list[str],
settings_url: str | None,
expected_output: str,
) -> None:
client = MagicMock()
mock_get_client.return_value = client
response = MagicMock()
response.json.return_value = {"dm_ai_engine_url": settings_url}
client.make_request.return_value = response

result = runner.invoke(app, ["system", "ai-engine", "show", *extra_args])

assert result.exit_code == 0
client.make_request.assert_called_once_with("GET", "/api/settings/")
assert expected_output in result.stdout


@patch(f"{MODULE}.get_client")
def test_ai_engine_set_patches_settings_with_url(mock_get_client: MagicMock, runner: CliRunner) -> None:
client = MagicMock()
mock_get_client.return_value = client

result = runner.invoke(app, ["system", "ai-engine", "set", "http://engine.example.com:9021"])

assert result.exit_code == 0
client.make_request.assert_called_once_with(
"PATCH", "/api/settings/", data={"dm_ai_engine_url": "http://engine.example.com:9021"}
)
Loading