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
2 changes: 2 additions & 0 deletions python/packages/jumpstarter-cli-common/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ dependencies = [
"pydantic>=2.8.2",
"click>=8.1.7.2",
"authlib>=1.4.1",
"certifi>=2024.2.2",
"requests>=2.28.0",
"truststore>=0.10.1",
"joserfc>=1.0.3",
"yarl>=1.18.3",
Expand Down
2 changes: 1 addition & 1 deletion python/packages/jumpstarter-cli/jumpstarter_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ async def refresh_token(config):
new_refresh_token = tokens.get("refresh_token")
if new_refresh_token is not None:
config.refresh_token = new_refresh_token
ClientConfigV1Alpha1.save(config) # ty: ignore[invalid-argument-type]
ClientConfigV1Alpha1.save(config)
click.echo("Access token refreshed.")
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def test_create_lease_passes_exporter_name_to_config():
lease = Mock()
config.create_lease.return_value = lease

assert create_lease.callback is not None
with patch("jumpstarter_cli.create.model_print") as model_print:
# Skip Click config loading wrapper and call the command body directly.
inspect.unwrap(create_lease.callback)(
Expand All @@ -36,6 +37,7 @@ def test_create_lease_passes_exporter_name_to_config():


def test_create_lease_requires_selector_or_name():
assert create_lease.callback is not None
with pytest.raises(click.UsageError, match="one of --selector/-l or --name/-n is required"):
inspect.unwrap(create_lease.callback)(
config=Mock(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
class TestBatchDeleteLeases:
def test_delete_multiple_leases(self):
config = Mock()
delete_leases.callback.__wrapped__.__wrapped__(
assert delete_leases.callback is not None
delete_leases.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
names=("lease1", "lease2", "lease3"),
selector=None,
Expand All @@ -24,8 +25,9 @@ def test_delete_multiple_leases(self):

def test_delete_zero_names_no_flags_raises_error(self):
config = Mock()
assert delete_leases.callback is not None
with pytest.raises(click.ClickException, match="must be specified"):
delete_leases.callback.__wrapped__.__wrapped__(
delete_leases.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
names=(),
selector=None,
Expand All @@ -38,7 +40,8 @@ def test_delete_with_output_name(self):
from jumpstarter_cli_common.opt import OutputMode

config = Mock()
delete_leases.callback.__wrapped__.__wrapped__(
assert delete_leases.callback is not None
delete_leases.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
names=("lease1", "lease2"),
selector=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def _make_config(leases):
return config


_delete_leases = delete_leases.callback.__wrapped__.__wrapped__
assert delete_leases.callback is not None
_delete_leases = delete_leases.callback.__wrapped__.__wrapped__ # ty: ignore[unresolved-attribute]


def test_delete_all_only_deletes_own_leases():
Expand Down
9 changes: 6 additions & 3 deletions python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,9 @@ def test_get_exporters_calls_list_exporters(self):

from jumpstarter_cli.get import get_exporters

assert get_exporters.callback is not None
with patch("jumpstarter_cli.get.model_print"):
get_exporters.callback.__wrapped__.__wrapped__(
get_exporters.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config, selector=None, output="text", with_options=[]
)

Expand All @@ -239,8 +240,9 @@ def test_get_leases_calls_list_leases(self):

from jumpstarter_cli.get import get_leases

assert get_leases.callback is not None
with patch("jumpstarter_cli.get.model_print"):
get_leases.callback.__wrapped__.__wrapped__(
get_leases.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config, selector=None, output="text", show_all=False, all_clients=False
)

Expand Down Expand Up @@ -398,7 +400,8 @@ def test_get_leases_accepts_short_a_flag(self):
assert "-a" in all_option.opts


_unwrapped_get_leases = get_leases.callback.__wrapped__.__wrapped__
assert get_leases.callback is not None
_unwrapped_get_leases = get_leases.callback.__wrapped__.__wrapped__ # ty: ignore[unresolved-attribute]


class TestGetLeasesClientFiltering:
Expand Down
4 changes: 2 additions & 2 deletions python/packages/jumpstarter-cli/jumpstarter_cli/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ async def relogin_client(config: ClientConfigV1Alpha1):
refresh_token = tokens.get("refresh_token")
if refresh_token is not None:
config.refresh_token = refresh_token
ClientConfigV1Alpha1.save(config) # ty: ignore[invalid-argument-type]
ClientConfigV1Alpha1.save(config)
return
except Exception:
pass
Expand All @@ -387,6 +387,6 @@ async def relogin_client(config: ClientConfigV1Alpha1):
refresh_token = tokens.get("refresh_token")
if refresh_token is not None:
config.refresh_token = refresh_token
ClientConfigV1Alpha1.save(config) # ty: ignore[invalid-argument-type]
ClientConfigV1Alpha1.save(config)
except Exception as e:
raise ReauthenticationFailed(f"Failed to re-authenticate: {e}") from e
2 changes: 1 addition & 1 deletion python/packages/jumpstarter-cli/jumpstarter_cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def signal_handler():

with open_signal_receiver(signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT) as signals:
async for sig in signals:
if signal_handled: # ty: ignore[unresolved-reference]
if signal_handled:
continue # Ignore duplicate signals
received_signal = sig
logger.info("CHILD: Received %d (%s)", received_signal, signal.Signals(received_signal).name)
Expand Down
29 changes: 17 additions & 12 deletions python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout):
yield lease

config.lease_async = lease_async
config.lease_async = lease_async # ty: ignore[invalid-assignment]

async def fake_monitor(_config, _lease, _cancel_scope, token_state=None):
if token_state is not None:
Expand Down Expand Up @@ -140,8 +140,9 @@
config = Mock(spec=ClientConfigV1Alpha1)
config.metadata = type("Metadata", (), {"name": "test-client"})()
config.list_leases = AsyncMock(return_value=_make_lease_list([]))
assert shell.callback is not None
with pytest.raises(click.UsageError, match="no active leases found"):
shell.callback.__wrapped__.__wrapped__(
shell.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
command=(),
lease_name=None,
Expand All @@ -157,6 +158,7 @@


def test_shell_allows_existing_lease_name_without_selector_or_name():
assert shell.callback is not None
with (
patch("jumpstarter_cli.shell.anyio.run", return_value=0),
patch("jumpstarter_cli.shell.sys.exit") as mock_exit,
Expand All @@ -181,11 +183,12 @@
def test_shell_auto_connects_single_lease():
config = Mock(spec=ClientConfigV1Alpha1)
config.metadata = type("Metadata", (), {"name": "test-client"})()
assert shell.callback is not None
with (
patch("jumpstarter_cli.shell.anyio.run", side_effect=["my-only-lease", 0]) as mock_run,
patch("jumpstarter_cli.shell.sys.exit") as mock_exit,
):
shell.callback.__wrapped__.__wrapped__(
shell.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
command=(),
lease_name=None,
Expand All @@ -211,8 +214,9 @@
config = Mock(spec=ClientConfigV1Alpha1)
config.metadata = type("Metadata", (), {"name": "test-client"})()
config.list_leases = AsyncMock(return_value=_make_lease_list([]))
assert shell.callback is not None
with pytest.raises(click.UsageError, match="no active leases found"):
shell.callback.__wrapped__.__wrapped__(
shell.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
command=(),
lease_name=None,
Expand Down Expand Up @@ -247,12 +251,13 @@
config = Mock(spec=ClientConfigV1Alpha1)
config.metadata = type("Metadata", (), {"name": "test-client"})()
config.list_leases = AsyncMock(return_value=_make_lease_list(["lease-a", "lease-b"]))
assert shell.callback is not None
with (
patch("jumpstarter_cli.shell.sys.stdin") as mock_stdin,
pytest.raises(click.UsageError, match="lease-a"),
):
mock_stdin.isatty.return_value = False
shell.callback.__wrapped__.__wrapped__(
shell.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
command=(),
lease_name=None,
Expand Down Expand Up @@ -286,8 +291,9 @@
config = Mock(spec=ClientConfigV1Alpha1)
config.metadata = type("Metadata", (), {"name": "test-client"})()
config.list_leases = AsyncMock(return_value=lease_list)
assert shell.callback is not None
with pytest.raises(click.UsageError, match="no active leases found"):
shell.callback.__wrapped__.__wrapped__(
shell.callback.__wrapped__.__wrapped__( # ty: ignore[unresolved-attribute]
config=config,
command=(),
lease_name=None,
Expand All @@ -303,6 +309,7 @@


def test_shell_allows_env_lease_without_selector_or_name():
assert shell.callback is not None
with (
patch("jumpstarter_cli.shell.anyio.run", return_value=0),
patch("jumpstarter_cli.shell.sys.exit") as mock_exit,
Expand Down Expand Up @@ -696,8 +703,7 @@

def check_cancelled():
nonlocal call_count
call_count += 1 # ty: ignore[unresolved-reference]
return call_count > 1
call_count += 1 return call_count > 1

Check failure on line 706 in python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py

View workflow job for this annotation

GitHub Actions / lint-python

ruff (invalid-syntax)

python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py:706:40: invalid-syntax: Simple statements must be separated by newlines or semicolons

config = _make_config()

Expand Down Expand Up @@ -899,9 +905,8 @@

async def get_status_race():
nonlocal call_count
call_count += 1 # ty: ignore[unresolved-reference]
if call_count == 1:
call_count += 1 if call_count == 1:

Check failure on line 908 in python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py

View workflow job for this annotation

GitHub Actions / lint-python

ruff (invalid-syntax)

python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py:908:58: invalid-syntax: Expected `else`, found `:`
return ExporterStatus.LEASE_READY

Check failure on line 909 in python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py

View workflow job for this annotation

GitHub Actions / lint-python

ruff (invalid-syntax)

python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py:909:1: invalid-syntax: Unexpected indentation

Check failure on line 909 in python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py

View workflow job for this annotation

GitHub Actions / lint-python

ruff (invalid-syntax)

python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py:908:59: invalid-syntax: Expected a statement
lease.lease_ended = True
return ExporterStatus.AVAILABLE

Expand All @@ -922,7 +927,7 @@
assert not monitor._connection_lost


class TestShellWithSignalHandlingLeaseTimeout:

Check failure on line 930 in python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py

View workflow job for this annotation

GitHub Actions / lint-python

ruff (invalid-syntax)

python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py:930:1: invalid-syntax: Expected a statement
async def test_exits_gracefully_when_lease_ended_and_exception_group(self):
"""BaseExceptionGroup with lease_ended=True should produce exit code 0."""
lease = Mock()
Expand All @@ -937,7 +942,7 @@
async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout):
yield lease

config.lease_async = lease_async
config.lease_async = lease_async # ty: ignore[invalid-assignment]

async def fake_run_shell(*_args):
raise BaseExceptionGroup("test", [RuntimeError("simulated cancellation")])
Expand Down Expand Up @@ -966,7 +971,7 @@
async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout):
yield lease

config.lease_async = lease_async
config.lease_async = lease_async # ty: ignore[invalid-assignment]

async def fake_run_shell(*_args):
raise BaseExceptionGroup("test", [RuntimeError("connection broken")])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def test_update_lease_with_to_client():
lease = Mock()
config.update_lease.return_value = lease

assert update_lease.callback is not None
with patch("jumpstarter_cli.update.model_print") as model_print:
inspect.unwrap(update_lease.callback)(
config=config,
Expand All @@ -39,6 +40,7 @@ def test_update_lease_with_duration_and_to_client():
lease = Mock()
config.update_lease.return_value = lease

assert update_lease.callback is not None
with patch("jumpstarter_cli.update.model_print") as model_print:
inspect.unwrap(update_lease.callback)(
config=config,
Expand All @@ -63,6 +65,7 @@ def test_update_lease_without_to_client():
lease = Mock()
config.update_lease.return_value = lease

assert update_lease.callback is not None
with patch("jumpstarter_cli.update.model_print") as model_print:
inspect.unwrap(update_lease.callback)(
config=config,
Expand All @@ -83,6 +86,7 @@ def test_update_lease_without_to_client():


def test_update_lease_requires_at_least_one_option():
assert update_lease.callback is not None
with pytest.raises(click.UsageError, match="At least one of"):
inspect.unwrap(update_lease.callback)(
config=Mock(),
Expand Down
24 changes: 12 additions & 12 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,25 @@ hatch-pin-jumpstarter = { workspace = true }

[dependency-groups]
docs = [
"sphinx<8.1.0",
"sphinx<9.0.0",
"myst-parser>=5.0.0",
"sphinxcontrib-mermaid>=2.0.1",
"furo>=2024.8.6",
"esbonio>=0.16.4",
"sphinx-autobuild>=2024.4.16",
"sphinx-click>=6.0.0",
"sphinx-substitution-extensions>=2024.10.17",
"furo>=2025.12.19",
"esbonio>=2.0.0",
"sphinx-autobuild>=2025.8.25",
"sphinx-click>=6.2.0",
"sphinx-substitution-extensions>=2026.1.12",
"requests>=2.33.1",
"sphinxcontrib-programoutput>=0.19",
"sphinx-copybutton>=0.5.2",
"sphinx-inline-tabs>=2023.4.21",
"sphinx-inline-tabs>=2025.12.21.14",
]
dev = [
"ruff==0.15.10",
"typos>=1.23.6",
"pre-commit>=3.8.0",
"esbonio>=0.16.5",
"ty>=0.0.1a8",
"ruff==0.15.11",
"typos>=1.45.1",
"pre-commit>=4.5.1",
"esbonio>=2.0.0",
"ty>=0.0.31",
"diff-cover>=10.2.0",
]

Expand Down
Loading
Loading