diff --git a/packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py b/packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py index 1e091fe88c..54334ae1d0 100644 --- a/packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py +++ b/packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py @@ -175,8 +175,9 @@ def test_run_config_rejects_aggregate_fields(self) -> None: def test_rejects_legacy_backend_argument(self): backend = _FakeDirectBackend(single_result=_empty_evaluation_result(), multi_result=_empty_benchmark_result()) + legacy_kwargs: dict = {"backend": backend} with pytest.raises(TypeError, match="backend"): - Evaluator(backend=backend) # type: ignore[call-arg] + Evaluator(**legacy_kwargs) @pytest.mark.asyncio async def test_run_uses_offline_params_without_request_fail_fast(self): diff --git a/packages/nmp_common/tests/api/test_parsed_filter.py b/packages/nmp_common/tests/api/test_parsed_filter.py index d7db7e440b..aa10e68920 100644 --- a/packages/nmp_common/tests/api/test_parsed_filter.py +++ b/packages/nmp_common/tests/api/test_parsed_filter.py @@ -127,7 +127,8 @@ def test_remove_missing_field(self): def test_remove_from_none(self): pf = ParsedFilter(operation=None, _field_map=SampleFilter._get_entity_field_map()) - assert pf.remove("status") is None + result = pf.remove("status") + assert result is None def test_remove_non_eq_not_removed(self): op = ComparisonOperation(operator=FilterOperator.LIKE, field="name", value="llama") diff --git a/packages/nmp_testing/src/nmp/testing/__init__.py b/packages/nmp_testing/src/nmp/testing/__init__.py index ab4f7b96b0..a3bb6e72a3 100644 --- a/packages/nmp_testing/src/nmp/testing/__init__.py +++ b/packages/nmp_testing/src/nmp/testing/__init__.py @@ -114,8 +114,6 @@ "cleanup_model_deployment_containers", "get_worker_port_range", "MOCK_NIM_NGINX_CONF", - "MOCK_NIM_IMAGE_TAG", - "MOCK_SIDECAR_IMAGE_TAG", "MODELS_CONTROLLER_MANAGED_LABEL", # Notebook testing "create_temp_venv_with_kernel", diff --git a/packages/nmp_testing/src/nmp/testing/docker.py b/packages/nmp_testing/src/nmp/testing/docker.py index 40e11463ba..9e203bfa37 100644 --- a/packages/nmp_testing/src/nmp/testing/docker.py +++ b/packages/nmp_testing/src/nmp/testing/docker.py @@ -135,29 +135,29 @@ def create_docker_client(fail_message: str | None = None) -> docker.DockerClient A validated Docker client. Raises: - pytest.fail: If Docker client cannot be created or daemon is not responding. + pytest.fail.Exception: If Docker client cannot be created or daemon is not responding. """ try: client = docker.from_env() except DockerException as e: msg = fail_message or "Docker client initialization failed" - pytest.fail( + raise pytest.fail.Exception( f"{msg}: {e}\n\n" "Please ensure Docker is installed and the Docker daemon is running:\n" " - macOS/Windows: Start Docker Desktop\n" " - Linux: Run 'sudo systemctl start docker' or 'sudo service docker start'\n" " - Verify with: 'docker info'" - ) + ) from e # Verify the daemon is actually responding try: client.ping() except DockerException as e: - pytest.fail( + raise pytest.fail.Exception( f"Docker daemon is not responding: {e}\n\n" "The Docker client was created but cannot communicate with the daemon.\n" "Please ensure the Docker daemon is running." - ) + ) from e return client diff --git a/packages/safe_synthesizer_sdk/src/safe_synthesizer_sdk/job_builder.py b/packages/safe_synthesizer_sdk/src/safe_synthesizer_sdk/job_builder.py index 1d6d0e1de2..f0fabd0ffb 100644 --- a/packages/safe_synthesizer_sdk/src/safe_synthesizer_sdk/job_builder.py +++ b/packages/safe_synthesizer_sdk/src/safe_synthesizer_sdk/job_builder.py @@ -8,9 +8,10 @@ import string import tempfile from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pandas as pd +from nemo_platform.types.safe_synthesizer import SafeSynthesizerJobConfigParam from typing_extensions import Self from .job import SafeSynthesizerJob @@ -227,13 +228,12 @@ def with_hf_token_secret(self, secret_name: str) -> Self: def _resolve_datasource(self, **kwargs) -> None: if self._data_source_path is not None: return # already uploaded; reuse the cached result - match self._data_source: - case pd.DataFrame() as df: - pass - case str(url): - df = pd.read_csv(url, **kwargs) - case _: - raise ValueError("Data source must be a pandas DataFrame or a URL") + if isinstance(self._data_source, pd.DataFrame): + df = self._data_source + elif isinstance(self._data_source, str): + df = pd.read_csv(self._data_source, **kwargs) + else: + raise ValueError("Data source must be a pandas DataFrame or a URL") tmp_path: Path | None = None try: @@ -310,8 +310,8 @@ def create_job(self, **kwargs) -> SafeSynthesizerJob: spec = self._build_job_spec() response = self._client.safe_synthesizer.jobs.create( workspace=self._workspace, - spec=spec, - **kwargs, # type: ignore # spec accepts dict at runtime + spec=cast(SafeSynthesizerJobConfigParam, spec), + **kwargs, ) return SafeSynthesizerJob(response.name, self._client, workspace=self._workspace) diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/skills/agents-secure/resources/pii_scan.py b/plugins/nemo-agents/src/nemo_agents_plugin/skills/agents-secure/resources/pii_scan.py index 5031e088c2..ed029b2a66 100755 --- a/plugins/nemo-agents/src/nemo_agents_plugin/skills/agents-secure/resources/pii_scan.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/skills/agents-secure/resources/pii_scan.py @@ -52,14 +52,6 @@ CONTEXT_WINDOW = 80 -@dataclass(frozen=True) -class PatternSpec: - name: str - regex: re.Pattern[str] - guard: Callable[[str, re.Match[str]], bool] | None = None - mask: Callable[[str], str] = lambda s: _mask_middle(s) - - # --------------------------------------------------------------------------- # # Masking helpers # --------------------------------------------------------------------------- # @@ -72,6 +64,14 @@ def _mask_middle(value: str, keep: int = 2) -> str: return f"{value[:keep]}{'*' * (len(value) - keep * 2)}{value[-keep:]}" +@dataclass(frozen=True) +class PatternSpec: + name: str + regex: re.Pattern[str] + guard: Callable[[str, re.Match[str]], bool] | None = None + mask: Callable[[str], str] = _mask_middle + + def _mask_email(value: str) -> str: local, _, domain = value.partition("@") if not domain: diff --git a/script/openapi_helper/openapi_tools.py b/script/openapi_helper/openapi_tools.py index 120bf31319..8f7961ad45 100644 --- a/script/openapi_helper/openapi_tools.py +++ b/script/openapi_helper/openapi_tools.py @@ -418,7 +418,7 @@ def schema_tree(spec_file: str = typer.Argument(..., help="Path to OpenAPI speci print_verbose("\n[bold magenta]Schema Dependency Tree[/bold magenta]") print_verbose("Top-level schemas (used directly in endpoints) are shown at the root level") print_verbose("Dependent schemas are shown as children\n") - print_verbose("Unused schemas: ", ", ".join(sorted(unused_schemas)), style="bold yellow") + print_verbose(f"Unused schemas: {', '.join(sorted(unused_schemas))}", style="bold yellow") print_schema_tree(tree) diff --git a/sdk/python/nemo-platform/src/nemo_platform/beta/safe_synthesizer/job_builder.py b/sdk/python/nemo-platform/src/nemo_platform/beta/safe_synthesizer/job_builder.py index 1d6d0e1de2..f0fabd0ffb 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/beta/safe_synthesizer/job_builder.py +++ b/sdk/python/nemo-platform/src/nemo_platform/beta/safe_synthesizer/job_builder.py @@ -8,9 +8,10 @@ import string import tempfile from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pandas as pd +from nemo_platform.types.safe_synthesizer import SafeSynthesizerJobConfigParam from typing_extensions import Self from .job import SafeSynthesizerJob @@ -227,13 +228,12 @@ def with_hf_token_secret(self, secret_name: str) -> Self: def _resolve_datasource(self, **kwargs) -> None: if self._data_source_path is not None: return # already uploaded; reuse the cached result - match self._data_source: - case pd.DataFrame() as df: - pass - case str(url): - df = pd.read_csv(url, **kwargs) - case _: - raise ValueError("Data source must be a pandas DataFrame or a URL") + if isinstance(self._data_source, pd.DataFrame): + df = self._data_source + elif isinstance(self._data_source, str): + df = pd.read_csv(self._data_source, **kwargs) + else: + raise ValueError("Data source must be a pandas DataFrame or a URL") tmp_path: Path | None = None try: @@ -310,8 +310,8 @@ def create_job(self, **kwargs) -> SafeSynthesizerJob: spec = self._build_job_spec() response = self._client.safe_synthesizer.jobs.create( workspace=self._workspace, - spec=spec, - **kwargs, # type: ignore # spec accepts dict at runtime + spec=cast(SafeSynthesizerJobConfigParam, spec), + **kwargs, ) return SafeSynthesizerJob(response.name, self._client, workspace=self._workspace) diff --git a/services/core/entities/src/nmp/core/entities/api/v2/entities/endpoints.py b/services/core/entities/src/nmp/core/entities/api/v2/entities/endpoints.py index 0b1eb0bf58..54d7f74b31 100644 --- a/services/core/entities/src/nmp/core/entities/api/v2/entities/endpoints.py +++ b/services/core/entities/src/nmp/core/entities/api/v2/entities/endpoints.py @@ -350,7 +350,12 @@ async def list_entities( filter_op=combined_filter, relationship_child_workspaces=accessible_workspaces, ) - elif accessible_workspaces is None or workspace in accessible_workspaces: + else: + raise_if_workspace_inaccessible( + accessible_workspaces, + workspace, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, + ) # Check if workspace is being deleted (404 for user requests) await validate_workspace_not_deleting(workspace_repository, auth_client, workspace) @@ -364,12 +369,6 @@ async def list_entities( filter_op=filter, relationship_child_workspaces=accessible_workspaces, ) - else: - raise_if_workspace_inaccessible( - accessible_workspaces, - workspace, - status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, - ) return EntitiesPage( data=entities, diff --git a/services/core/inference-gateway/tests/integration/test_middleware_pipeline.py b/services/core/inference-gateway/tests/integration/test_middleware_pipeline.py index 8f94209b2a..891e63d673 100644 --- a/services/core/inference-gateway/tests/integration/test_middleware_pipeline.py +++ b/services/core/inference-gateway/tests/integration/test_middleware_pipeline.py @@ -208,6 +208,7 @@ class ModelRouterMiddleware(NemoInferenceMiddleware): REQUEST_MUTATION_KEY = "x_original_model" def __init__(self, target_model_entity_id: str) -> None: + super().__init__() self._target = target_model_entity_id async def on_startup(self) -> None: diff --git a/services/evaluator/src/nmp/evaluator/app/jobs/metric_results.py b/services/evaluator/src/nmp/evaluator/app/jobs/metric_results.py index ef5a493775..823a26132d 100644 --- a/services/evaluator/src/nmp/evaluator/app/jobs/metric_results.py +++ b/services/evaluator/src/nmp/evaluator/app/jobs/metric_results.py @@ -4,6 +4,7 @@ import asyncio import json import logging +from typing import cast import nmp.evaluator.app.values as app import nmp.evaluator.entities as entities @@ -129,9 +130,17 @@ async def register_result_entity( log.info("Registering result entity", extra={"aggregate_scores_path": aggregate_scores_path}) if getattr(job, "metric", None): - result_entity = load_metric_result_entity(aggregate_scores_path, job, config) + result_entity = load_metric_result_entity( + aggregate_scores_path, + cast("app.MetricJob", job), + config, + ) elif getattr(job, "benchmark", None): - result_entity = load_benchmark_result_entity(aggregate_scores_path, job, config) + result_entity = load_benchmark_result_entity( + aggregate_scores_path, + cast("app.BenchmarkJob", job), + config, + ) else: raise ValueError(f"unsupported job {type(job)}") @@ -167,9 +176,11 @@ def load_benchmark_result_entity( if isinstance(job.benchmark, app.Benchmark): metric_refs = [metric.metric_ref for metric in job.benchmark.metrics] dataset_ref = job.benchmark.dataset - benchmark_ref = job.benchmark.name + benchmark_ref = app.BenchmarkRef(root=job.benchmark.name) elif isinstance(job.benchmark, app.SystemBenchmark): - benchmark_ref = f"{SYSTEM_WORKSPACE}/{job.benchmark.name}" + benchmark_ref = app.BenchmarkRef(root=f"{SYSTEM_WORKSPACE}/{job.benchmark.name}") + else: + raise ValueError(f"Unsupported benchmark type: {type(job.benchmark).__name__}") return entities.BenchmarkJobResult( name=config.NEMO_JOB_ID, diff --git a/services/safe-synthesizer/src/nmp/safe_synthesizer/tasks/safe_synthesizer/__main__.py b/services/safe-synthesizer/src/nmp/safe_synthesizer/tasks/safe_synthesizer/__main__.py index 8458625054..6a8c182a2f 100644 --- a/services/safe-synthesizer/src/nmp/safe_synthesizer/tasks/safe_synthesizer/__main__.py +++ b/services/safe-synthesizer/src/nmp/safe_synthesizer/tasks/safe_synthesizer/__main__.py @@ -302,12 +302,10 @@ def run_task(): enable_synthesis: bool = job_config.get("enable_synthesis", True) logger.info(f"enable_synthesis={enable_synthesis}") - nss_job_config: SafeSynthesizerJobConfig - match job_config: - case dict(): - nss_job_config = SafeSynthesizerJobConfig.model_validate(job_config) - case _: - raise ValueError(f"Config must be a dictionary or a string: {job_config}") + if isinstance(job_config, dict): + nss_job_config = SafeSynthesizerJobConfig.model_validate(job_config) + else: + raise ValueError(f"Config must be a dictionary: {job_config}") logger.info(f"Nemo Safe Synthesizer runtime job config: {nss_job_config.model_dump_json(indent=2)}") save_path = Path(os.environ.get(EPHEMERAL_TASK_STORAGE_PATH_ENVVAR, DEFAULT_TASK_STORAGE_PATH)) diff --git a/tests/agentic-use/jobs-execute-gpu-cli/tests/test_outputs.py b/tests/agentic-use/jobs-execute-gpu-cli/tests/test_outputs.py index 7e2e160eb9..d3a2465cca 100644 --- a/tests/agentic-use/jobs-execute-gpu-cli/tests/test_outputs.py +++ b/tests/agentic-use/jobs-execute-gpu-cli/tests/test_outputs.py @@ -118,6 +118,7 @@ def test_agent_polled_status() -> None: commands = session.get_bash_commands() except Exception: pytest.skip("trace_reader not available") + return status_checks = [ cmd for cmd in commands if "jobs" in cmd and ("get-status" in cmd or "get_status" in cmd or "status" in cmd) @@ -134,6 +135,7 @@ def test_agent_investigated_failure() -> None: commands = session.get_bash_commands() except Exception: pytest.skip("trace_reader not available") + return fail_investigation = [cmd for cmd in commands if "gpu-fail-job" in cmd or "fail-job" in cmd or "fail_job" in cmd] assert len(fail_investigation) >= 2, (