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
43 changes: 38 additions & 5 deletions nipyapi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,40 @@ def _apply_verbosity(verbosity):
# verbosity 0: leave as default (WARNING or unset)


def _apply_profile(explicit_profile):
"""
Configure the SDK connection from the selected profile before dispatch.

When an explicit ``--profile`` was given, a resolution failure is fatal: emit
a structured error and exit non-zero rather than silently falling back to the
SDK default (localhost). A misconfigured explicit profile that fell through
would otherwise surface as a confusing localhost connection error on the first
API call, masking the real cause.

When no profile was given (auto-resolve), a ValueError is non-fatal - there may
simply be no configuration yet, and any error surfaces on the first API call.
This preserves the AWS-CLI-style "just works without explicit config" behaviour.
"""
import nipyapi

try:
nipyapi.profiles.switch(explicit_profile)
except ValueError as e:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why check for none after making a call? Would it be better to move this check outside the try block?

if explicit_profile is None:
# Auto-resolve: no configuration found - errors surface on first API call
return
output_format = _detect_output_format()
error_result = {
"success": False,
"error": str(e),
"error_type": type(e).__name__,
"command": "profiles.switch",
"profile": explicit_profile,
}
print(_serialize_result(error_result, output_format))
sys.exit(1)


def main():
"""CLI entry point."""
# Disable pager for help output so agents don't hang waiting for input
Expand Down Expand Up @@ -490,11 +524,10 @@ def main():

# Auto-configure NiFi connection.
# Priority: explicit --profile arg > NIFI_API_ENDPOINT > NIPYAPI_PROFILE > first profile
# This matches AWS CLI / gcloud pattern - just works without explicit config
try:
nipyapi.profiles.switch(explicit_profile)
except ValueError:
pass # No configuration found - errors will surface on first API call
# This matches AWS CLI / gcloud pattern - just works without explicit config.
# An explicit --profile that fails to resolve is fatal (see _apply_profile);
# auto-resolve failures fall through so errors surface on the first API call.
_apply_profile(explicit_profile)

# Create CLI interface with docstring that Fire will display in help
# pylint: disable=too-many-instance-attributes,too-few-public-methods
Expand Down
60 changes: 60 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import json
import os

import pytest


# =============================================================================
# Helper Function Tests (no NiFi connection required)
Expand Down Expand Up @@ -977,6 +979,64 @@ def test_log_capture_handler():
logger.removeHandler(handler)


# =============================================================================
# Profile application Tests (no NiFi connection required)
# =============================================================================


def test_apply_profile_explicit_failure_exits(monkeypatch, capsys):
"""An explicit --profile that fails to resolve must exit non-zero and name the
profile - not silently fall back to the SDK default (localhost)."""
import nipyapi
from nipyapi import cli

def _raise(*args, **kwargs):
raise ValueError("Profile 'bogus' not found. Available: []")

monkeypatch.setattr(nipyapi.profiles, "switch", _raise)

with pytest.raises(SystemExit) as exc_info:
cli._apply_profile("bogus")

assert exc_info.value.code == 1
out = capsys.readouterr().out
# The error must name the failing profile, not a localhost connection error
assert "bogus" in out
assert "localhost" not in out


def test_apply_profile_autoresolve_failure_passes(monkeypatch):
"""No --profile (auto-resolve) failure is non-fatal: errors surface later on the
first API call, preserving the just-works-without-config behaviour."""
import nipyapi
from nipyapi import cli

def _raise(*args, **kwargs):
raise ValueError("No configuration found.")

monkeypatch.setattr(nipyapi.profiles, "switch", _raise)

# Should NOT raise or exit
cli._apply_profile(None)


def test_apply_profile_success_forwards_profile(monkeypatch):
"""A successful switch forwards the explicit profile name and returns normally."""
import nipyapi
from nipyapi import cli

called = {}

def _ok(profile_name=None, *args, **kwargs):
called["profile"] = profile_name
return (profile_name, None)

monkeypatch.setattr(nipyapi.profiles, "switch", _ok)

cli._apply_profile("prod")
assert called["profile"] == "prod"


# =============================================================================
# SafeModule Wrapper Tests (requires NiFi connection)
# =============================================================================
Expand Down
Loading