diff --git a/src/nxscli/cli/types.py b/src/nxscli/cli/types.py index 58a5fee..f2ddf8c 100644 --- a/src/nxscli/cli/types.py +++ b/src/nxscli/cli/types.py @@ -49,7 +49,7 @@ def get_list_from_str2( ############################################################################### -class Channels(click.ParamType): +class Channels(click.ParamType[list[ChannelRef]]): """Parse channels argument.""" name = "channels" @@ -86,7 +86,7 @@ def convert(self, value: Any, param: Any, ctx: Any) -> list[ChannelRef]: ############################################################################### -class Samples(click.ParamType): +class Samples(click.ParamType[int]): """Parse samples argument.""" name = "samples" @@ -106,7 +106,7 @@ def convert(self, value: Any, param: Any, ctx: Any) -> int: ############################################################################### -class Trigger(click.ParamType): +class Trigger(click.ParamType[dict[int, DTriggerConfigReq]]): """Parse trigger argument.""" name = "trigger" @@ -227,7 +227,7 @@ def convert( ############################################################################### -class Divider(click.ParamType): +class Divider(click.ParamType[list[int] | int]): """Parse divider argument.""" name = "divider" @@ -255,7 +255,7 @@ def convert(self, value: Any, param: Any, ctx: Any) -> list[int] | int: ############################################################################### -class StringList(click.ParamType): +class StringList(click.ParamType[list[str]]): """Parse a string list argument.""" name = "stringlist" @@ -275,7 +275,7 @@ def convert(self, value: Any, param: Any, ctx: Any) -> list[str]: ############################################################################### -class StringList2(click.ParamType): # pragma: no cover +class StringList2(click.ParamType[list[list[str]]]): # pragma: no cover """Parse a string list argument (2 separators).""" name = "stringlist2" diff --git a/src/nxscli/plugins/csv.py b/src/nxscli/plugins/csv.py index d6b990d..a3a8a80 100644 --- a/src/nxscli/plugins/csv.py +++ b/src/nxscli/plugins/csv.py @@ -76,19 +76,21 @@ def _handle_blocks( if rows <= 0: # pragma: no cover break - data_rows = (tuple(row) for row in block_data[:rows]) - meta_rows: Any + out_rows: Any if block.meta is None: - meta_rows = (() for _ in range(rows)) + out_rows = (list(row) for row in block_data[:rows]) elif self._meta_string: - meta_rows = ( - bytes(np.asarray(mrow, dtype=np.uint8)).decode() - for mrow in block.meta[:rows] + out_rows = ( + [*row, bytes(np.asarray(mrow, dtype=np.uint8)).decode()] + for row, mrow in zip(block_data[:rows], block.meta[:rows]) ) else: - meta_rows = (tuple(mrow) for mrow in block.meta[:rows]) + out_rows = ( + [*row, *mrow] + for row, mrow in zip(block_data[:rows], block.meta[:rows]) + ) - writer.writerows(zip(data_rows, meta_rows)) + writer.writerows(out_rows) self._datalen[j] += rows def start(self, kwargs: Any) -> bool: diff --git a/tests/plugins/test_csv.py b/tests/plugins/test_csv.py index b45b9e6..2362340 100644 --- a/tests/plugins/test_csv.py +++ b/tests/plugins/test_csv.py @@ -39,6 +39,66 @@ def test_plugincsv_handle_blocks_none_meta_and_empty_block() -> None: assert plugin._datalen == [2] +def test_plugincsv_handle_blocks_numeric_columns() -> None: + plugin = PluginCsv() + out = io.StringIO() + writer = csv.writer( + out, + delimiter=" ", + quotechar="|", + escapechar="\\", + quoting=csv.QUOTE_MINIMAL, + ) + plugin._csvwriters = [[writer, out]] + plugin._samples = 10 + plugin._nostop = False + plugin._datalen = [0] + plugin._meta_string = False + pdata = type("Q", (), {"vdim": 3})() + + block = DNxscopeStreamBlock( + data=np.array([[0.5966366, 0.119444996, 10.011609]], dtype=np.float32), + meta=None, + ) + plugin._handle_blocks([block], pdata, 0) + + value = out.getvalue() + # values must be exported as plain numbers, not "np.float32(...)" + assert "np.float32" not in value + # each value must land in its own column (space-delimited) + assert value.strip() == "0.5966366 0.119444996 10.011609" + + +def test_plugincsv_handle_blocks_numeric_columns_with_meta() -> None: + plugin = PluginCsv() + out = io.StringIO() + writer = csv.writer( + out, + delimiter=" ", + quotechar="|", + escapechar="\\", + quoting=csv.QUOTE_MINIMAL, + ) + plugin._csvwriters = [[writer, out]] + plugin._samples = 10 + plugin._nostop = False + plugin._datalen = [0] + plugin._meta_string = False + pdata = type("Q", (), {"vdim": 1})() + + block = DNxscopeStreamBlock( + data=np.array([[1.0], [2.0]]), + meta=np.array([[7, 8], [9, 10]]), + ) + plugin._handle_blocks([block], pdata, 0) + + assert "np.float32" not in out.getvalue() + # meta values are appended as extra columns + lines = out.getvalue().strip().splitlines() + assert lines[0] == "1.0 7 8" + assert lines[1] == "2.0 9 10" + + def test_plugincsv_handle_blocks_meta_string() -> None: plugin = PluginCsv() out = io.StringIO()