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
8 changes: 8 additions & 0 deletions e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@

import pytest
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.files.client import FilesClient

from e2e.services_pool import E2EServicesPool, RunningServices, admin_headers

Expand Down Expand Up @@ -259,6 +261,12 @@ def sdk(_services: str, _services_instance: RunningServices) -> NeMoPlatform:
)


@pytest.fixture(scope="module")
def files_client(sdk: NeMoPlatform) -> FilesClient:
"""Provide a FilesClient derived from the SDK."""
return client_from_platform(sdk, FilesClient)


@pytest.fixture(scope="function")
def workspace(sdk: NeMoPlatform) -> Iterator[str]:
"""Create a unique workspace for each test, deleted on teardown."""
Expand Down
10 changes: 6 additions & 4 deletions e2e/files/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@

import pytest
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.files.client import FilesClient
from nemo_platform_plugin.files.types import CreateFilesetRequest
from nemo_platform_plugin.files.types import FilesetOutput as Fileset


@pytest.fixture
def fileset(sdk: NeMoPlatform, workspace: str) -> Iterator[Fileset]:
def fileset(files_client: FilesClient, workspace: str) -> Iterator[Fileset]:
"""Create a unique fileset for each test with automatic cleanup."""
fileset_name = f"e2e-fileset-{uuid.uuid4().hex[:8]}"
fileset = sdk.files.filesets.create(workspace=workspace, name=fileset_name)
fileset = files_client.create_fileset(body=CreateFilesetRequest(name=fileset_name), workspace=workspace).data()
yield fileset
try:
sdk.files.filesets.delete(fileset_name, workspace=workspace)
files_client.delete_fileset(name=fileset_name, workspace=workspace)
except Exception:
pass # Ignore cleanup errors
pass


def test_file_upload_and_download(sdk: NeMoPlatform, workspace: str, fileset: Fileset):
Expand Down
96 changes: 55 additions & 41 deletions e2e/files/test_storage_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
from pathlib import Path

import pytest
from nemo_platform import BadRequestError, NeMoPlatform
from nemo_platform.types.files import HuggingfaceStorageConfigParam, NGCStorageConfigParam
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.errors import BadRequestError
from nemo_platform_plugin.files.client import FilesClient
from nemo_platform_plugin.files.storage_config import HuggingfaceStorageConfig, NGCStorageConfig
from nemo_platform_plugin.files.types import CreateFilesetRequest

# ---------------------------------------------------------------------------
# NGC configuration
Expand All @@ -40,26 +43,28 @@


@pytest.fixture
def ngc_fileset(sdk: NeMoPlatform, workspace: str, ngc_secret: str) -> Iterator[str]:
def ngc_fileset(files_client: FilesClient, workspace: str, ngc_secret: str) -> Iterator[str]:
"""Create an NGC-backed fileset, cleaned up after test."""
fileset_name = f"e2e-ngc-fs-{uuid.uuid4().hex[:8]}"
sdk.files.filesets.create(
files_client.create_fileset(
workspace=workspace,
name=fileset_name,
description="E2E test NGC-backed fileset",
storage=NGCStorageConfigParam(
api_key_secret=ngc_secret,
org=NGC_ORG,
team=NGC_TEAM,
target=NGC_TARGET,
target_type=NGC_TARGET_TYPE,
body=CreateFilesetRequest(
name=fileset_name,
description="E2E test NGC-backed fileset",
storage=NGCStorageConfig(
api_key_secret=ngc_secret,
org=NGC_ORG,
team=NGC_TEAM,
target=NGC_TARGET,
target_type=NGC_TARGET_TYPE,
),
),
)
yield fileset_name
try:
sdk.files.filesets.delete(fileset_name, workspace=workspace)
files_client.delete_fileset(name=fileset_name, workspace=workspace)
except Exception:
pass # Best-effort cleanup; the workspace is deleted anyway
pass


@pytest.fixture
Expand All @@ -83,27 +88,29 @@ def hf_secret(sdk: NeMoPlatform, workspace: str, hf_token: str) -> Iterator[str]


@pytest.fixture
def hf_fileset(sdk: NeMoPlatform, workspace: str, hf_secret: str) -> Iterator[str]:
def hf_fileset(files_client: FilesClient, workspace: str, hf_secret: str) -> Iterator[str]:
"""Create a Hugging Face-backed fileset, cleaned up after test."""
fileset_name = f"e2e-hf-fs-{uuid.uuid4().hex[:8]}"

storage = HuggingfaceStorageConfigParam(
storage = HuggingfaceStorageConfig(
repo_id=HF_REPO_ID,
repo_type=HF_REPO_TYPE,
token_secret=hf_secret,
)

sdk.files.filesets.create(
files_client.create_fileset(
workspace=workspace,
name=fileset_name,
description="E2E test HF-backed fileset",
storage=storage,
body=CreateFilesetRequest(
name=fileset_name,
description="E2E test HF-backed fileset",
storage=storage,
),
)
yield fileset_name
try:
sdk.files.filesets.delete(fileset_name, workspace=workspace)
files_client.delete_fileset(name=fileset_name, workspace=workspace)
except Exception:
pass # Best-effort cleanup; the workspace is deleted anyway
pass


# ===================================================================
Expand Down Expand Up @@ -182,6 +189,7 @@ def test_cache_status(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str)
def test_create_error(
self,
sdk: NeMoPlatform,
files_client: FilesClient,
workspace: str,
ngc_api_key: str,
secret_value: str | None,
Expand All @@ -193,34 +201,38 @@ def test_create_error(
secret_name = f"e2e-ngc-err-{uuid.uuid4().hex[:8]}"
sdk.secrets.create(workspace=workspace, name=secret_name, value=value)
try:
storage = NGCStorageConfigParam(
storage = NGCStorageConfig(
api_key_secret=secret_name,
org=storage_overrides.get("org", NGC_ORG),
team=storage_overrides.get("team", NGC_TEAM),
target=storage_overrides.get("target", NGC_TARGET),
target_type=NGC_TARGET_TYPE,
)
with pytest.raises(BadRequestError, match=match):
sdk.files.filesets.create(
files_client.create_fileset(
workspace=workspace,
name=f"e2e-ngc-err-{uuid.uuid4().hex[:8]}",
storage=storage,
body=CreateFilesetRequest(
name=f"e2e-ngc-err-{uuid.uuid4().hex[:8]}",
storage=storage,
),
)
finally:
sdk.secrets.delete(workspace=workspace, name=secret_name)

def test_create_error_nonexistent_secret(self, sdk: NeMoPlatform, workspace: str):
def test_create_error_nonexistent_secret(self, files_client: FilesClient, workspace: str):
"""Referencing a secret that doesn't exist is rejected with 400."""
with pytest.raises(BadRequestError, match="Secret not found:"):
sdk.files.filesets.create(
files_client.create_fileset(
workspace=workspace,
name=f"e2e-ngc-err-{uuid.uuid4().hex[:8]}",
storage=NGCStorageConfigParam(
api_key_secret="no-such-secret-99999",
org=NGC_ORG,
team=NGC_TEAM,
target=NGC_TARGET,
target_type=NGC_TARGET_TYPE,
body=CreateFilesetRequest(
name=f"e2e-ngc-err-{uuid.uuid4().hex[:8]}",
storage=NGCStorageConfig(
api_key_secret="no-such-secret-99999",
org=NGC_ORG,
team=NGC_TEAM,
target=NGC_TARGET,
target_type=NGC_TARGET_TYPE,
),
),
)

Expand Down Expand Up @@ -276,14 +288,16 @@ def test_cache_status(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str):

# -- error cases --

def test_error_nonexistent_repo(self, sdk: NeMoPlatform, workspace: str):
def test_error_nonexistent_repo(self, files_client: FilesClient, workspace: str):
"""Pointing at a repo that doesn't exist is rejected with 400."""
with pytest.raises(BadRequestError):
sdk.files.filesets.create(
files_client.create_fileset(
workspace=workspace,
name=f"e2e-hf-err-{uuid.uuid4().hex[:8]}",
storage=HuggingfaceStorageConfigParam(
repo_id="this-org-does-not-exist/this-repo-does-not-exist-12345",
repo_type="model",
body=CreateFilesetRequest(
name=f"e2e-hf-err-{uuid.uuid4().hex[:8]}",
storage=HuggingfaceStorageConfig(
repo_id="this-org-does-not-exist/this-repo-does-not-exist-12345",
repo_type="model",
),
),
)
17 changes: 11 additions & 6 deletions e2e/test_data_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
from data_designer_nemo.fileset_file_seed_source import FilesetFileSeedSource
from data_designer_nemo.nemotron_personas import WORKSPACE, get_resource_name_for_locale
from nemo_data_designer_plugin.sdk.errors import DataDesignerJobError
from nemo_platform import NeMoPlatform, NotFoundError
from nemo_platform import NeMoPlatform
from nemo_platform.types.inference import ModelProvider
from nemo_platform_plugin.client.errors import NotFoundError
from nemo_platform_plugin.files.client import FilesClient
from nemo_platform_plugin.files.types import CreateFilesetRequest
from nmp.testing import MockProviderResponse, add_mock_provider, assert_exit_0, run_nemo_local
from nmp.testing.pytest_outcomes import pytest_skip

Expand Down Expand Up @@ -132,12 +135,12 @@ def test_simple_ndd_config(sdk: NeMoPlatform, workspace: str) -> None:
_assert_dataset_equal(job_dataset, expected_job_dataset)


def test_fileset_seed_data(sdk: NeMoPlatform, workspace: str) -> None:
def test_fileset_seed_data(sdk: NeMoPlatform, files_client: FilesClient, workspace: str) -> None:
"""Tests that the Data Designer *library* plugin that makes Filesets available as seed sources
is wired up properly by the Data Designer *platform plugin*.
"""
fileset_name = "my-fileset"
sdk.files.filesets.create(name=fileset_name, workspace=workspace)
files_client.create_fileset(body=CreateFilesetRequest(name=fileset_name), workspace=workspace)

seed_data = pd.DataFrame(data={"seed": ["my-seed"]})
remote_path = "data.parquet"
Expand Down Expand Up @@ -170,7 +173,9 @@ def test_fileset_seed_data(sdk: NeMoPlatform, workspace: str) -> None:


@pytest.fixture
def nemotron_personas_locale(_services: str, sdk: NeMoPlatform, workspace: str, ngc_secret: str) -> Generator[str]:
def nemotron_personas_locale(
_services: str, files_client: FilesClient, workspace: str, ngc_secret: str
) -> Generator[str]:
"""Invokes the CLI to create a Fileset for Nemotron Personas data.

This test does call out to NGC and downloads personas data. Use the smallest locale available
Expand All @@ -184,7 +189,7 @@ def nemotron_personas_locale(_services: str, sdk: NeMoPlatform, workspace: str,

fileset_name = get_resource_name_for_locale(locale)
with suppress(NotFoundError):
sdk.files.filesets.delete(fileset_name, workspace=WORKSPACE)
files_client.delete_fileset(name=fileset_name, workspace=WORKSPACE)

result = run_nemo_local(
"data-designer",
Expand All @@ -202,7 +207,7 @@ def nemotron_personas_locale(_services: str, sdk: NeMoPlatform, workspace: str,
yield locale

with suppress(NotFoundError):
sdk.files.filesets.delete(fileset_name, workspace=WORKSPACE)
files_client.delete_fileset(name=fileset_name, workspace=WORKSPACE)


def test_nemotron_personas_sampling(sdk: NeMoPlatform, workspace: str, nemotron_personas_locale: str) -> None:
Expand Down
9 changes: 7 additions & 2 deletions e2e/test_jobs_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import pytest
from nemo_platform import NeMoPlatform
from nemo_platform_ext.auth.helpers import generate_unsigned_jwt
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.files.client import FilesClient
from nemo_platform_plugin.files.types import CreateFilesetRequest
from nemo_platform_plugin.jobs.api_factory import (
ContainerSpec,
CPUExecutionProviderSpec,
Expand Down Expand Up @@ -121,7 +124,8 @@ def test_job_principal_propagation(sdk: NeMoPlatform):
assert completed_job.status == "completed"

fileset_name = f"hello-world-{job.name}"
fileset = user_sdk.files.filesets.retrieve(workspace=workspace_name, name=fileset_name)
files = client_from_platform(user_sdk, FilesClient)
fileset = files.get_fileset(workspace=workspace_name, name=fileset_name).data()
assert fileset is not None

file_content = user_sdk.files.download_content(
Expand Down Expand Up @@ -150,7 +154,8 @@ def test_job_cannot_access_unauthorized_workspace(sdk: NeMoPlatform):
other_sdk = _as_bearer_user(sdk, other_email)

fileset_name = "private-data"
owner_sdk.files.filesets.create(workspace=restricted_workspace, name=fileset_name)
files = client_from_platform(owner_sdk, FilesClient)
files.create_fileset(workspace=restricted_workspace, body=CreateFilesetRequest(name=fileset_name))

job = other_sdk.jobs.create(
workspace=runner_workspace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from data_designer_nemo.sdk_translation import async_to_sync_sdk
from nemo_platform import AsyncNeMoPlatform, NeMoPlatform
from nemo_platform.filesets import FilesetFileSystem
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.files.client import FilesClient

workspace_cvar = ContextVar[str | None]("workspace_cvar", default=None)

Expand All @@ -26,7 +28,8 @@ def create_duckdb_connection(self) -> duckdb.DuckDBPyConnection:
if self._sdk is None:
raise RuntimeError("FilesetFileSeedReader requires an injected NeMo Platform SDK")

filesystem = FilesetFileSystem(sdk=self._sdk)
files_client = client_from_platform(self._sdk, FilesClient)
filesystem = FilesetFileSystem(client=files_client)

conn = duckdb.connect()
conn.register_filesystem(filesystem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
from typing import Literal

from data_designer.config.utils.constants import NEMOTRON_PERSONAS_DATASET_SIZES
from nemo_platform import ConflictError, NeMoPlatform
from nemo_platform.types.files import NGCStorageConfigParam
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.client.errors import ConflictError
from nemo_platform_plugin.files.client import FilesClient
from nemo_platform_plugin.files.storage_config import NGCStorageConfig
from nemo_platform_plugin.files.types import CreateFilesetRequest

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -90,20 +94,23 @@ def sync_nemotron_personas_fileset(


def _create_fileset(sdk: NeMoPlatform, locale: str, api_key_secret: str) -> None:
sdk.files.filesets.create(
files = client_from_platform(sdk, FilesClient)
files.create_fileset(
workspace=WORKSPACE,
name=get_resource_name_for_locale(locale),
description=f"Nemotron Personas dataset for locale: {locale!r}",
purpose="dataset",
storage=_get_storage_config_for_locale(locale, api_key_secret),
cache=True,
body=CreateFilesetRequest(
name=get_resource_name_for_locale(locale),
description=f"Nemotron Personas dataset for locale: {locale!r}",
purpose="dataset",
storage=_get_storage_config_for_locale(locale, api_key_secret),
cache=True,
),
)


def _get_storage_config_for_locale(locale: str, api_key_secret: str) -> NGCStorageConfigParam:
def _get_storage_config_for_locale(locale: str, api_key_secret: str) -> NGCStorageConfig:
resource_name = get_resource_name_for_locale(locale)

return NGCStorageConfigParam(
return NGCStorageConfig(
api_key_secret=api_key_secret,
org=NGC_ORG,
team=NGC_TEAM,
Expand Down
Loading