Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
197 changes: 112 additions & 85 deletions e2e/test_entities.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from nemo_iron_swarm_plugin.entities import IRON_SWARM_RUN_TYPE
from nemo_iron_swarm_plugin.filesets import download_fileset
from nemo_platform_plugin.authz import CallerKind, path_rule
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient
from pydantic import BaseModel
from starlette.concurrency import run_in_threadpool

Expand Down Expand Up @@ -153,10 +155,14 @@ def _fileset_fallback(workspace: str, name: str, stream: Any, after: int) -> lis
sdk = _get_sdk()
# get_entity_by_name returns a generic Entity — its domain fields live under `.data`
# (same access pattern as sdk.py::_run_to_dict), not as top-level attributes.
run = sdk.entities.get_entity_by_name(
name=name,
entity_type=IRON_SWARM_RUN_TYPE,
workspace=workspace,
run = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(
name=name,
entity_type=IRON_SWARM_RUN_TYPE,
workspace=workspace,
)
.data()
)
fileset_ref = (getattr(run, "data", None) or {}).get("events_fileset")
if fileset_ref:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
from nemo_iron_swarm_plugin.entities import IRON_SWARM_MANIFEST_TYPE
from nemo_iron_swarm_plugin.filesets import download_and_extract_project, upload_project_dir
from nemo_iron_swarm_plugin.jobs.errors import CATEGORY_FILESET, CATEGORY_MANIFEST, IronSwarmRunError
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient
from nemo_platform_plugin.entities.types import EntityUpdate
from nemo_platform_plugin.job_context import JobContext

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -147,8 +150,10 @@ def _materialize_manifest(
raise IronSwarmRunError(
CATEGORY_MANIFEST, "running a saved manifest requires the platform SDK (submit the job, don't run locally)."
)
record = sdk.entities.get_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace)
.data()
)
data = {**(getattr(record, "data", {}) or {}), **(config_overrides or {})}
manifest_dir = ctx.storage.persistent
Expand Down Expand Up @@ -255,8 +260,11 @@ def _persist_upgraded_bundle(sdk: Any, manifest_id: str, ctx: JobContext, record
updated = {**(getattr(record, "data", {}) or {})}
updated["agent_fileset"] = fileset
updated["manifest_yaml"] = yaml.safe_dump(resolved.manifest, sort_keys=False)
sdk.entities.update_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace, data=updated
client_from_platform(sdk, EntitiesClient).update_entity_by_name(
name=manifest_id,
entity_type=IRON_SWARM_MANIFEST_TYPE,
workspace=ctx.workspace,
body=EntityUpdate(data=updated),
)
except Exception: # the war-game matters more than the upgrade; it retries next run
logger.warning("could not freeze manifest %s on this run", manifest_id, exc_info=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
)
from nemo_iron_swarm_plugin.jobs import benign_suite
from nemo_iron_swarm_plugin.jobs.errors import RunFailure
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient
from nemo_platform_plugin.entities.types import EntityCreateInput, EntityUpdate
from nemo_platform_plugin.entity_client import NemoEntitiesClient
from nemo_platform_plugin.job_context import JobContext

Expand Down Expand Up @@ -74,7 +77,11 @@ def _create_run(sdk: Any, *, workspace: str, data: dict[str, Any]) -> str | None
if sdk is None or not hasattr(sdk, "entities"):
return None
try:
entity = sdk.entities.create(IRON_SWARM_RUN_TYPE, workspace=workspace, data=data)
entity = (
client_from_platform(sdk, EntitiesClient)
.create_entity(entity_type=IRON_SWARM_RUN_TYPE, workspace=workspace, body=EntityCreateInput(data=data))
.data()
)
return getattr(entity, "name", None)
except Exception: # recording is best-effort, not part of the war-game
logger.warning("failed to persist IronSwarmRun record", exc_info=True)
Expand Down Expand Up @@ -120,7 +127,11 @@ def _run_facts(sdk: Any, *, workspace: str, name: str) -> tuple[str, int]:
if sdk is None or not hasattr(sdk, "entities"):
return "", 0
try:
record = sdk.entities.get_entity_by_name(name=name, entity_type=IRON_SWARM_RUN_TYPE, workspace=workspace)
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=name, entity_type=IRON_SWARM_RUN_TYPE, workspace=workspace)
.data()
)
data = getattr(record, "data", {}) or {}
port = data.get("port")
return str(data.get("agent") or ""), int(port) if isinstance(port, int) else 0
Expand All @@ -134,7 +145,9 @@ def _update_run(sdk: Any, *, workspace: str, name: str, data: dict[str, Any]) ->
if sdk is None or not hasattr(sdk, "entities"):
return
try:
sdk.entities.update_entity_by_name(name=name, entity_type=IRON_SWARM_RUN_TYPE, workspace=workspace, data=data)
client_from_platform(sdk, EntitiesClient).update_entity_by_name(
name=name, entity_type=IRON_SWARM_RUN_TYPE, workspace=workspace, body=EntityUpdate(data=data)
).data()
except Exception: # recording is best-effort, not part of the war-game
logger.warning("failed to update IronSwarmRun record", exc_info=True)

Expand All @@ -144,8 +157,10 @@ def _manifest_rounds(sdk: Any, manifest_id: str, ctx: JobContext) -> int:
if sdk is None or not hasattr(sdk, "entities"):
return 1
try:
record = sdk.entities.get_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace)
.data()
)
rounds = (getattr(record, "data", {}) or {}).get("rounds")
return rounds if isinstance(rounds, int) and rounds >= 1 else 1
Expand All @@ -159,8 +174,10 @@ def _manifest_models(sdk: Any, manifest_id: str, ctx: JobContext) -> dict[str, A
if sdk is None or not hasattr(sdk, "entities"):
return {}
try:
record = sdk.entities.get_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace)
.data()
)
models = (getattr(record, "data", {}) or {}).get("models")
return models if isinstance(models, dict) else {}
Expand All @@ -174,8 +191,10 @@ def _cached_benign_suite(sdk: Any, manifest_id: str, ctx: JobContext) -> list[di
if sdk is None or not hasattr(sdk, "entities"):
return []
try:
record = sdk.entities.get_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=ctx.workspace)
.data()
)
suite = (getattr(record, "data", {}) or {}).get("benign_suite") or []
return [row for row in suite if isinstance(row, dict)]
Expand Down Expand Up @@ -216,15 +235,17 @@ def _persist_benign_suite(
if sdk is None or not hasattr(sdk, "entities") or not suite:
return
try:
record = sdk.entities.get_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=workspace
record = (
client_from_platform(sdk, EntitiesClient)
.get_entity_by_name(name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=workspace)
.data()
)
data = dict(getattr(record, "data", {}) or {})
data["benign_suite"] = suite
if interview:
data["benign_interview"] = interview
sdk.entities.update_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=workspace, data=data
client_from_platform(sdk, EntitiesClient).update_entity_by_name(
name=manifest_id, entity_type=IRON_SWARM_MANIFEST_TYPE, workspace=workspace, body=EntityUpdate(data=data)
)
except Exception: # caching is best-effort, not part of the war-game
logger.warning("failed to cache benign suite on manifest %s", manifest_id, exc_info=True)
15 changes: 13 additions & 2 deletions plugins/nemo-iron-swarm/src/nemo_iron_swarm_plugin/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
from nemo_iron_swarm_plugin.jobs.run import IronSwarmRunJob
from nemo_iron_swarm_plugin.jobs.synth_benign import IronSwarmSynthBenignJob
from nemo_platform import AsyncNeMoPlatform, NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient
from nemo_platform_plugin.entities.types import ListEntitiesQueryParams
from nemo_platform_plugin.scheduler import NemoJobScheduler
from nemo_platform_plugin.sdk import NemoPluginSDKResources

Expand Down Expand Up @@ -108,8 +111,16 @@ def _list_newest(platform: NeMoPlatform, entity_type: str, *, workspace: str, li
``page_size`` bounds the *page*, not the total — iterating it walks the entire history. We ask for
one page of *limit* and take only that page's items, which is a single request.
"""
page = platform.entities.list(entity_type, workspace=workspace, sort="-created_at", page_size=limit)
return [_run_to_dict(item) for item in itertools.islice(page, limit)]
page = (
client_from_platform(platform, EntitiesClient)
.list_entities(
entity_type=entity_type,
workspace=workspace,
query_params=ListEntitiesQueryParams(sort="-created_at", page_size=limit),
)
.page()
)
return [_run_to_dict(item) for item in itertools.islice(page.items, limit)]


class _RunsResource:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import pytest
from nemo_platform import ConflictError, NeMoPlatform, PermissionDeniedError
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient
from nemo_platform_plugin.entities.types import EntityCreateInput
from nmp.core.entities.service import EntitiesService
from nmp.testing import TEST_USER_EMAIL, create_test_client, short_unique_name

Expand Down Expand Up @@ -560,12 +563,14 @@ def test_delete_workspace_with_entities_marks_for_deletion(self, sdk: NeMoPlatfo

# Create entity as service principal (generic entities API requires service credentials)
with as_service(sdk, "entities"):
sdk.entities.create(
client_from_platform(sdk, EntitiesClient).create_entity(
workspace=workspace_name,
entity_type="test-entity-type",
name="test-entity",
data={"key": "value"},
)
body=EntityCreateInput(
name="test-entity",
data={"key": "value"},
),
).data()

with as_user(sdk, admin_email):
sdk.workspaces.delete(workspace_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import pytest
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient

WORKSPACE = "default"

Expand All @@ -27,16 +29,20 @@ def client() -> NeMoPlatform:

def test_harbor_test_model_deleted(client: NeMoPlatform) -> None:
"""Test that harbor-test-model was deleted after CRUD operations."""
response = client.entities.list(entity_type="model")
entity_names = [e.name for e in response.data]
response = client_from_platform(client, EntitiesClient).list_entities(entity_type="model")
entity_names = [e.name for e in response.page().items]
Comment thread
maxdubrinsky marked this conversation as resolved.
Outdated
assert "harbor-test-model" not in entity_names, (
f"Entity 'harbor-test-model' should have been deleted but still exists! Found: {entity_names}"
)


def test_harbor_final_dataset_exists(client: NeMoPlatform) -> None:
"""Test that harbor-final-dataset was created and has correct data."""
response = client.entities.get_entity_by_name(name="harbor-final-dataset", entity_type="dataset")
response = (
client_from_platform(client, EntitiesClient)
.get_entity_by_name(name="harbor-final-dataset", entity_type="dataset")
.data()
)
assert response.name == "harbor-final-dataset", (
f"Expected entity name 'harbor-final-dataset', got '{response.name}'"
)
Expand Down
12 changes: 9 additions & 3 deletions tests/agentic-use/entities-basic-cli/tests/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import pytest
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import EntitiesClient

WORKSPACE = "default"

Expand All @@ -27,16 +29,20 @@ def client() -> NeMoPlatform:

def test_harbor_test_model_deleted(client: NeMoPlatform) -> None:
"""Test that harbor-test-model was deleted after CRUD operations."""
response = client.entities.list(entity_type="model")
entity_names = [e.name for e in response.data]
response = client_from_platform(client, EntitiesClient).list_entities(entity_type="model")
entity_names = [e.name for e in response.page().items]
assert "harbor-test-model" not in entity_names, (
f"Entity 'harbor-test-model' should have been deleted but still exists! Found: {entity_names}"
)


def test_harbor_final_dataset_exists(client: NeMoPlatform) -> None:
"""Test that harbor-final-dataset was created and has correct data."""
response = client.entities.get_entity_by_name(name="harbor-final-dataset", entity_type="dataset")
response = (
client_from_platform(client, EntitiesClient)
.get_entity_by_name(name="harbor-final-dataset", entity_type="dataset")
.data()
)
assert response.name == "harbor-final-dataset", (
f"Expected entity name 'harbor-final-dataset', got '{response.name}'"
)
Expand Down
Loading