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
34 changes: 17 additions & 17 deletions src/pieces/core/list_command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections.abc import Iterable
import threading

from pieces.settings import Settings
from pieces._vendor.pieces_os_client.wrapper.basic_identifier.asset import BasicAsset
Expand All @@ -24,28 +23,29 @@ def list_command(cls, **kwargs):
def list_assets(cls, **kwargs):
from pieces.utils import PiecesSelectMenu

assets = kwargs.get(
"assets",
[BasicAsset(item.id) for item in BasicAsset.get_identifiers()], # type: ignore[assignment]
)
assets = kwargs.get("assets")
if assets is None:
assets = [BasicAsset(item.id) for item in BasicAsset.get_identifiers()]

menu_entries = []
for index, asset in enumerate(assets, start=1):
try:
menu_entries.append(
(f"{index}: {asset.name}", {"asset_id": asset.id, **kwargs})
)
except AttributeError:
continue

if not menu_entries:
Settings.logger.print("No materials available to select.")
return

select_menu = PiecesSelectMenu(
[],
menu_entries,
AssetsCommands.open_asset,
kwargs.get("footer"),
title="Select a material",
)

def update_assets():
for i, asset in enumerate(assets, start=1):
try:
select_menu.add_entry(
(f"{i}: {asset.name}", {"asset_id": asset.id, **kwargs})
)
except AttributeError:
pass

threading.Thread(target=update_assets).start()
select_menu.run()

@classmethod
Expand Down
88 changes: 64 additions & 24 deletions tests/assets/list_assets_test.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
import pytest
from pieces.app import main
from tests.utils import capture_stderr, restore_stderr, mock_select_menus, SCRIPT_NAME
from unittest.mock import patch, Mock
from pieces.settings import Settings
import sys
from pieces.core.assets_command import AssetsCommands
from pieces.core.list_command import ListCommand

MODULE_NAME = "pieces.core.list_command"
OPEN_MODULE_NAME = "pieces.core.assets_command"


@pytest.fixture
def mock_sys_argv_drive():
with patch("sys.argv", [SCRIPT_NAME, "drive"]):
yield
@patch("pieces.utils.PiecesSelectMenu")
@patch(f"{MODULE_NAME}.BasicAsset")
@patch(f"{OPEN_MODULE_NAME}.Settings.pieces_client.assets")
def test_list_assets_builds_menu_entries_from_default_asset_lookup(
mock_assets_api, mock_basic_asset, mock_menu_cls, mock_assets
):
mock_assets_api.return_value = mock_assets
mock_basic_asset.get_identifiers.return_value = [
Mock(id=asset.id) for asset in mock_assets
]
mock_basic_asset.side_effect = lambda asset_id: next(
asset for asset in mock_assets if asset.id == asset_id
)

ListCommand.list_assets()

def test_list_assets(mock_api_client, mock_assets, mock_settings, mock_sys_argv_drive):
def main_func():
with (
patch(f"{MODULE_NAME}.Settings.pieces_client", mock_api_client),
patch(f"{MODULE_NAME}.BasicAsset") as mock_basic_asset,
):
mock_basic_asset.get_identifiers.return_value = mock_assets
main()
Settings.logger.debug(mock_api_client)
mock_menu_cls.assert_called_once()
menu_entries, on_enter, footer = mock_menu_cls.call_args.args

expected_assets = [
(f"{i}: {asset.name}", {"ITEM_INDEX": i, "show_warning": False})
for i, asset in enumerate(mock_assets, start=1)
assert footer is None
assert on_enter == AssetsCommands.open_asset
assert [label for label, _ in menu_entries] == [
f"{index}: {asset.name}" for index, asset in enumerate(mock_assets, start=1)
]

# Call the mock select menus
mock_select_menus(
main_func, "pieces.utils", [], None, AssetsCommands.open_asset, expected_assets
)
assert [payload["asset_id"] for _, payload in menu_entries] == [
asset.id for asset in mock_assets
]
mock_menu_cls.return_value.run.assert_called_once()


@patch("shutil.which")
Expand Down Expand Up @@ -66,3 +67,42 @@ def test_open_asset_success(mock_run, mock_assets, mock_basic_asset, mock_sh, tm

assert result is None
mock_run.assert_called_once()


@patch("pieces.utils.PiecesSelectMenu")
@patch(f"{OPEN_MODULE_NAME}.Settings.pieces_client.assets")
def test_list_assets_builds_menu_entries_with_asset_ids(
mock_assets_api, mock_menu_cls, mock_assets
):
mock_assets_api.return_value = mock_assets

ListCommand.list_assets(assets=mock_assets, footer="Search results")

mock_menu_cls.assert_called_once()
menu_entries, on_enter, footer = mock_menu_cls.call_args.args

assert footer == "Search results"
assert on_enter == AssetsCommands.open_asset
assert len(menu_entries) == len(mock_assets)
assert [label for label, _ in menu_entries] == [
f"{index}: {asset.name}" for index, asset in enumerate(mock_assets, start=1)
]
assert [payload["asset_id"] for _, payload in menu_entries] == [
asset.id for asset in mock_assets
]
mock_menu_cls.return_value.run.assert_called_once()
mock_menu_cls.return_value.add_entry.assert_not_called()


@patch.object(Settings, "logger")
@patch("pieces.utils.PiecesSelectMenu")
@patch(f"{OPEN_MODULE_NAME}.Settings.pieces_client.assets")
def test_list_assets_skips_menu_when_no_entries_can_be_built(
mock_assets_api, mock_menu_cls, mock_logger
):
mock_assets_api.return_value = [object()]

ListCommand.list_assets(assets=[object()])

mock_menu_cls.assert_not_called()
mock_logger.print.assert_called_once_with("No materials available to select.")