diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx index 24b26b63ef..c118633a94 100644 --- a/docs/cli/reference.mdx +++ b/docs/cli/reference.mdx @@ -16,6 +16,12 @@ nemo workspaces list --output-format markdown nemo workspaces get default -f json ``` +**Exit codes:** +- 0: Success +- 1: Local or unexpected error +- 2: Command usage error +- 3: Remote/API error + **Usage:** ```shell @@ -25,7 +31,7 @@ nemo [GLOBAL OPTIONS] COMMAND [ARGS]... **Global Options:** * `--base-url`: Base URL for the NeMo Platform API -* `--output-format, -f `: Output format for how results are printed. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for how results are printed. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output * `--timestamp-format `: Timestamp format for table/markdown/csv output [possible values: relative, iso8601] * `--verbose, -v`: Enable verbose messaging. This only impacts logs that are visible, it doesn't change any data outputs. @@ -655,9 +661,10 @@ nemo skills list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo skills show @@ -1063,9 +1070,10 @@ nemo plugins list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ## Core plugins @@ -1206,7 +1214,7 @@ nemo files list [OPTIONS] FILESET **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. * `--no-truncate`: Don't truncate long values in table/markdown/csv output. @@ -1315,7 +1323,7 @@ Example: metadata = FilesetMetadata( dataset=DatasetMetadataContent( schema=`{"c **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo files filesets delete @@ -1385,9 +1393,10 @@ Filter filesets by name, description, purpose, storage_type, created_at, and upd **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo files filesets get @@ -1415,7 +1424,7 @@ nemo files filesets get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo files filesets update @@ -1462,7 +1471,7 @@ Example: metadata = FilesetMetadata( dataset=DatasetMetadataContent( schema=`{"c **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo files otlp @@ -1542,7 +1551,7 @@ nemo files otlp logs create [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo files otlp logs query @@ -1575,7 +1584,7 @@ nemo files otlp logs query [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo inference @@ -1703,7 +1712,7 @@ nemo inference deployment-configs create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployment-configs delete @@ -1772,9 +1781,10 @@ Filter deployment configs by workspace, project, model_entity_id, name, descript **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference deployment-configs get @@ -1800,7 +1810,7 @@ nemo inference deployment-configs get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployment-configs update @@ -1847,7 +1857,7 @@ nemo inference deployment-configs update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployment-configs versions @@ -1921,9 +1931,10 @@ nemo inference deployment-configs versions list [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ###### nemo inference deployment-configs versions get @@ -1950,7 +1961,7 @@ nemo inference deployment-configs versions get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo inference deployments @@ -2021,7 +2032,7 @@ nemo inference deployments create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] **Wait Options:** @@ -2110,9 +2121,10 @@ Filter deployments by workspace, project, status, config, model_provider_id, nam **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference deployments list-models @@ -2144,9 +2156,10 @@ nemo inference deployments list-models [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference deployments get @@ -2172,7 +2185,7 @@ nemo inference deployments get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployments update @@ -2216,7 +2229,7 @@ nemo inference deployments update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployments update-status @@ -2265,7 +2278,7 @@ nemo inference deployments update-status [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference deployments versions @@ -2350,9 +2363,10 @@ nemo inference deployments versions list [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ###### nemo inference deployments versions get @@ -2379,7 +2393,7 @@ nemo inference deployments versions get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo inference gateway @@ -2485,7 +2499,7 @@ nemo inference gateway model get [OPTIONS] TRAILING_URI **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway model patch @@ -2536,7 +2550,7 @@ nemo inference gateway model patch [OPTIONS] TRAILING_URI **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway model post @@ -2587,7 +2601,7 @@ nemo inference gateway model post [OPTIONS] TRAILING_URI [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway model put @@ -2638,7 +2652,7 @@ nemo inference gateway model put [OPTIONS] TRAILING_URI [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference gateway openai @@ -2725,7 +2739,7 @@ nemo inference gateway openai v1 models get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] **nemo inference gateway openai v1 models list** @@ -2752,9 +2766,10 @@ nemo inference gateway openai v1 models list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference gateway provider @@ -2827,7 +2842,7 @@ nemo inference gateway provider get [OPTIONS] TRAILING_URI **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway provider patch @@ -2871,7 +2886,7 @@ nemo inference gateway provider patch [OPTIONS] TRAILING_URI **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway provider post @@ -2915,7 +2930,7 @@ nemo inference gateway provider post [OPTIONS] TRAILING_URI [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway provider put @@ -2959,7 +2974,7 @@ nemo inference gateway provider put [OPTIONS] TRAILING_URI [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ###### nemo inference gateway provider ready @@ -2992,7 +3007,7 @@ nemo inference gateway provider ready [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo inference models @@ -3043,7 +3058,7 @@ nemo inference models get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference models list @@ -3070,9 +3085,10 @@ nemo inference models list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo inference prompts @@ -3146,7 +3162,7 @@ nemo inference prompts create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference prompts delete @@ -3207,9 +3223,10 @@ Filter prompts by workspace, project, name, description, created_at, and updated **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference prompts get @@ -3235,7 +3252,7 @@ nemo inference prompts get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference prompts update @@ -3284,7 +3301,7 @@ nemo inference prompts update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo inference providers @@ -3363,7 +3380,7 @@ nemo inference providers create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference providers delete @@ -3427,9 +3444,10 @@ Filter model providers by workspace, project, status, model_deployment_id, name, **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference providers get @@ -3455,7 +3473,7 @@ nemo inference providers get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference providers update @@ -3510,7 +3528,7 @@ nemo inference providers update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference providers update-status @@ -3564,7 +3582,7 @@ nemo inference providers update-status [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo inference virtual-models @@ -3639,7 +3657,7 @@ nemo inference virtual-models create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference virtual-models delete @@ -3707,9 +3725,10 @@ Filter virtual models by workspace, project, name, default_model_entity, created **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo inference virtual-models patch @@ -3759,7 +3778,7 @@ nemo inference virtual-models patch [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo inference virtual-models get @@ -3785,7 +3804,7 @@ nemo inference virtual-models get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo jobs @@ -3842,7 +3861,7 @@ nemo jobs cancel [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs create @@ -3892,7 +3911,7 @@ nemo jobs create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs delete @@ -3946,9 +3965,10 @@ nemo jobs get-logs [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo jobs get-status @@ -3974,7 +3994,7 @@ nemo jobs get-status [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs list @@ -4014,9 +4034,10 @@ Filter jobs by workspace, project, name, status, source, created_at, and updated **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo jobs list-execution-profiles @@ -4034,9 +4055,10 @@ nemo jobs list-execution-profiles [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo jobs pause @@ -4062,7 +4084,7 @@ nemo jobs pause [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs resume @@ -4088,7 +4110,7 @@ nemo jobs resume [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs get @@ -4114,7 +4136,7 @@ nemo jobs get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs update-status-details @@ -4157,7 +4179,7 @@ nemo jobs update-status-details [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs results @@ -4223,7 +4245,7 @@ nemo jobs results create [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo jobs results download @@ -4274,9 +4296,10 @@ nemo jobs results list [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo jobs results get @@ -4303,7 +4326,7 @@ nemo jobs results get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs steps @@ -4363,9 +4386,10 @@ Filter steps by job, status, and source. **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo jobs steps get @@ -4392,7 +4416,7 @@ nemo jobs steps get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo jobs steps update-status @@ -4438,7 +4462,7 @@ nemo jobs steps update-status [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo jobs tasks @@ -4506,7 +4530,7 @@ nemo jobs tasks create-or-update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo jobs tasks list @@ -4533,9 +4557,10 @@ nemo jobs tasks list [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo jobs tasks get @@ -4563,7 +4588,7 @@ nemo jobs tasks get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo models @@ -4645,7 +4670,7 @@ nemo models create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo models delete @@ -4719,9 +4744,10 @@ Filter models by name, project, workspace, base_model, adapters, finetuning_type **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo models get @@ -4751,7 +4777,7 @@ nemo models get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo models update @@ -4809,7 +4835,7 @@ nemo models update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo models adapters @@ -4877,7 +4903,7 @@ nemo models adapters create [OPTIONS] MODEL_NAME [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo models adapters delete @@ -4949,7 +4975,7 @@ nemo models adapters update [OPTIONS] ADAPTER **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo secrets @@ -4999,7 +5025,7 @@ nemo secrets access [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo secrets create @@ -5041,7 +5067,7 @@ nemo secrets create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo secrets delete @@ -5088,9 +5114,10 @@ nemo secrets list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo secrets get @@ -5116,7 +5143,7 @@ nemo secrets get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo secrets update @@ -5158,7 +5185,7 @@ nemo secrets update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo secrets admin @@ -5194,7 +5221,7 @@ nemo secrets admin rotate-encryption-keys [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo workspaces @@ -5273,7 +5300,7 @@ nemo workspaces create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo workspaces delete @@ -5348,9 +5375,10 @@ nemo workspaces list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo workspaces get @@ -5379,7 +5407,7 @@ nemo workspaces get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo workspaces update @@ -5427,7 +5455,7 @@ nemo workspaces update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo workspaces members @@ -5501,7 +5529,7 @@ nemo workspaces members create [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo workspaces members delete @@ -5566,9 +5594,10 @@ nemo workspaces members list [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo workspaces members update @@ -5624,7 +5653,7 @@ nemo workspaces members update [OPTIONS] PRINCIPAL_ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ## Functional plugins @@ -5708,7 +5737,7 @@ nemo guardrail check [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo guardrail configs @@ -5775,7 +5804,7 @@ nemo guardrail configs create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo guardrail configs delete @@ -5837,9 +5866,10 @@ Filter guardrail configs by name, description, project, created_at, and updated_ **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo guardrail configs get @@ -5865,7 +5895,7 @@ nemo guardrail configs get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo guardrail configs update @@ -5910,7 +5940,7 @@ nemo guardrail configs update [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo experiments @@ -5981,7 +6011,7 @@ nemo experiments create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo experiments delete @@ -6040,9 +6070,10 @@ Filter experiments by name, insight_id, is_deleted, or a metadata key/value (fil **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo experiments get @@ -6068,7 +6099,7 @@ nemo experiments get [OPTIONS] NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo experiments update @@ -6117,7 +6148,7 @@ nemo experiments update [OPTIONS] PATH_NAME **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ### nemo intake @@ -6211,7 +6242,7 @@ nemo intake annotations create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo intake annotations delete @@ -6274,9 +6305,10 @@ Filter annotations by span_id, session_id, kind, name, created_by, and created_a **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo intake annotations get @@ -6302,7 +6334,7 @@ nemo intake annotations get [OPTIONS] ANNOTATION_ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo intake evaluator-results @@ -6371,7 +6403,7 @@ nemo intake evaluator-results create [OPTIONS] [NAME] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo intake evaluator-results list @@ -6411,9 +6443,10 @@ Filter evaluator results by span_id, session_id, name, data_type, created_by, va **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo intake evaluator-results get @@ -6439,7 +6472,7 @@ nemo intake evaluator-results get [OPTIONS] EVALUATOR_RESULT_ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo intake ingest @@ -6526,7 +6559,7 @@ nemo intake ingest atif create [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo intake ingest chat-completions @@ -6592,7 +6625,7 @@ nemo intake ingest chat-completions create [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo intake ingest otlp @@ -6682,7 +6715,7 @@ nemo intake ingest otlp v1 traces create [OPTIONS] **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo intake sessions @@ -6726,7 +6759,7 @@ nemo intake sessions get [OPTIONS] ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] #### nemo intake spans @@ -6801,9 +6834,10 @@ Filter spans by session_id, trace_id, parent_span_id, project, evaluation contex **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo intake spans get @@ -6829,7 +6863,7 @@ nemo intake spans get [OPTIONS] SPAN_ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] ##### nemo intake spans evaluator-results @@ -6873,9 +6907,10 @@ nemo intake spans evaluator-results list [OPTIONS] SPAN_ID **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo intake spans groups @@ -6947,9 +6982,10 @@ Filter spans by the same fields as the span list endpoint, then group matching s **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. #### nemo intake traces @@ -7008,9 +7044,10 @@ Filter root-span-backed traces by id, session_id, root status, root span started **Output Options:** -* `--output-format, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] +* `--output-format, --output, -f `: Output format for the list of results. [possible values: table, json, yaml, markdown, csv, raw, code] * `--no-truncate`: Don't truncate long values in table/markdown/csv output. * `--output-columns, -c`: Columns to display: 'default', 'all', or comma-separated names. Only affects table/csv/markdown formats. +* `--stream`: Emit newline-delimited JSON, one record per line. Requires JSON or raw output. ##### nemo intake traces get @@ -7037,4 +7074,4 @@ nemo intake traces get [OPTIONS] ID **Output Options:** -* `--output-format, -f `: Output format for an entity. [possible values: json, yaml, raw, code] +* `--output-format, --output, -f `: Output format for an entity. [possible values: json, yaml, raw, code] diff --git a/docs/cli/working-with-resources.mdx b/docs/cli/working-with-resources.mdx index e1b1952770..24ed9ae23d 100644 --- a/docs/cli/working-with-resources.mdx +++ b/docs/cli/working-with-resources.mdx @@ -6,7 +6,7 @@ This page covers how to list, view, and create resources with the CLI. ## Output Formats -Control how output is displayed with the `--output-format` (or `-f`) option. +Control how output is displayed with the `--output-format` (or `--output`, `-f`) option. ### Table (Default for Lists) @@ -26,6 +26,12 @@ nemo workspaces list -f json nemo workspaces get default -f json | jq '.id' ``` +For list commands, add `--stream` with JSON or raw output to emit one newline-delimited JSON record per item: + +```bash +nemo workspaces list --output json --stream | jq -c '.' +``` + ### YAML YAML output, useful for configuration files: diff --git a/docs/fern/snippets/_snippets/cli-summary.mdx b/docs/fern/snippets/_snippets/cli-summary.mdx index f9ff088b0c..a2da083cff 100644 --- a/docs/fern/snippets/_snippets/cli-summary.mdx +++ b/docs/fern/snippets/_snippets/cli-summary.mdx @@ -7,7 +7,7 @@ description: "" | Option | Description | |--------|-------------| | `--base-url` | Base URL for the NeMo Platform API | -| `--output-format, -f ` | Output format for how results are printed. [possible values: table, json, yaml, markdown, csv, raw, code] | +| `--output-format, --output, -f ` | Output format for how results are printed. [possible values: table, json, yaml, markdown, csv, raw, code] | | `--no-truncate` | Don't truncate long values in table/markdown/csv output | | `--timestamp-format ` | Timestamp format for table/markdown/csv output [possible values: relative, iso8601] | | `--verbose, -v` | Enable verbose messaging. This only impacts logs that are visible, it doesn't change any data outputs. | diff --git a/packages/nemo_platform_ext/README.md b/packages/nemo_platform_ext/README.md index 2bedcccd2a..0ab81be445 100644 --- a/packages/nemo_platform_ext/README.md +++ b/packages/nemo_platform_ext/README.md @@ -197,7 +197,7 @@ nemo workspaces get my-workspace --output-format code ## Output Formats -Supports multiple output formats via `--output-format` / `-f`: +Supports multiple output formats via `--output-format` / `--output` / `-f`: - **table** (default) - Rich-formatted table with borders and colors - **json** - Syntax-highlighted JSON @@ -207,6 +207,8 @@ Supports multiple output formats via `--output-format` / `-f`: - **code** - Python SDK code equivalent - **raw** - Compact JSON without formatting +For list commands, combine `--stream` with `--output json` or `--output raw` to emit one newline-delimited JSON record per item. + ## Output Columns Customize columns in list commands with `--output-columns`: diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/app.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/app.py index f90d09d314..4880e747e0 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/app.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/app.py @@ -7,7 +7,7 @@ import logging from importlib.metadata import EntryPoint -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING, Annotated, cast import typer @@ -24,6 +24,7 @@ TopLevelEntry, build_top_level_entries, ) +from nemo_platform_ext.config.types import OutputFormat as ConfigOutputFormat if TYPE_CHECKING: from nemo_platform_plugin.cli import NemoCLI @@ -173,6 +174,7 @@ def main( ListOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", help="Output format for how results are printed.", rich_help_panel="Global Options", @@ -242,6 +244,12 @@ def main( [green]Examples:[/] nemo workspaces list --output-format markdown nemo workspaces get default -f json + + [green]Exit codes:[/] + - 0: Success + - 1: Local or unexpected error + - 2: Command usage error + - 3: Remote/API error """ # Lazy imports for performance (avoid loading pydantic_settings for --help) from nemo_platform_ext.cli.core.context import CLIContext @@ -274,7 +282,7 @@ def main( if base_url is not None: overrides["base_url"] = base_url if output_format is not None: - overrides["output_format"] = output_format + overrides["output_format"] = cast(ConfigOutputFormat, output_format) elif agent_mode: overrides["output_format"] = "markdown" if timestamp_format is not None: diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/adapters.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/adapters.py index 3d39408f1c..884b443439 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/adapters.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/adapters.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="adapters", help="Manage adapters") @@ -224,11 +230,13 @@ def list_adapters( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Adapters""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -279,6 +287,7 @@ def list_adapters( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/experiments.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/experiments.py index 2f9ead053d..1374b28f85 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/experiments.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/experiments.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="experiments", help="Manage experiments") @@ -191,11 +197,13 @@ def list_experiments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Experiments""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -238,6 +246,7 @@ def list_experiments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py index 065b03a19a..29f2315748 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="filesets", help="Manage filesets") @@ -210,6 +216,7 @@ def list_filesets( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Filesets endpoint with filtering and pagination. @@ -218,6 +225,7 @@ def list_filesets( updated_at via query parameters. Returns paginated results with sorting options.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -266,6 +274,7 @@ def list_filesets( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/guardrail/configs.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/guardrail/configs.py index 9bc6a4068f..2cb173d8e9 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/guardrail/configs.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/guardrail/configs.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="configs", help="Manage configs") @@ -160,6 +166,7 @@ def list_configs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List available guardrail configs. @@ -167,6 +174,7 @@ def list_configs( Lists guardrail configs for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -209,6 +217,7 @@ def list_configs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/iam/role_bindings.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/iam/role_bindings.py index 4ab4efe8de..110fffe500 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/iam/role_bindings.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/iam/role_bindings.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="role_bindings", help="Manage role_bindings") @@ -178,11 +184,13 @@ def list_role_bindings( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all role bindings (Platform Admin only)""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -231,6 +239,7 @@ def list_role_bindings( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/__init__.py index 7f70d57bb5..7c13b61f75 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/__init__.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_versions = _importlib_import_module( @@ -221,6 +227,7 @@ def list_deployment_configs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List ModelDeploymentConfigs for a specific workspace. @@ -229,6 +236,7 @@ def list_deployment_configs( version of each config.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -278,6 +286,7 @@ def list_deployment_configs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/versions.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/versions.py index 3e64fd2c66..356d0f5a1d 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/versions.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployment_configs/versions.py @@ -12,13 +12,19 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="versions", help="Manage versions") @@ -61,10 +67,12 @@ def list_versions( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all versions of a ModelDeploymentConfig.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -94,6 +102,7 @@ def list_versions( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/__init__.py index e1120e9c8b..d44083738b 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/__init__.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -23,6 +28,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) from nemo_platform_ext.cli.core.waiters import wait_for_inference_deployment @@ -250,6 +256,7 @@ def list_deployments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List ModelDeployments for a specific workspace. @@ -257,6 +264,7 @@ def list_deployments( By default, returns only the latest version of each deployment.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -309,6 +317,7 @@ def list_deployments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -324,6 +333,7 @@ def list_models_deployments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """Get Latest ModelDeployment's Model Entities from Entity Store. @@ -334,6 +344,7 @@ def list_models_deployments( TODO: Implement model entity retrieval based on deployment config.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -363,6 +374,7 @@ def list_models_deployments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/versions.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/versions.py index d1ab56a5ba..4417405d3b 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/versions.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/deployments/versions.py @@ -12,13 +12,19 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="versions", help="Manage versions") @@ -72,10 +78,12 @@ def list_versions( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all versions of a ModelDeployment.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -105,6 +113,7 @@ def list_versions( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/gateway/openai/v1/models.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/gateway/openai/v1/models.py index 0ccfca5497..652be2b9db 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/gateway/openai/v1/models.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/gateway/openai/v1/models.py @@ -12,13 +12,19 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="models", help="Manage models") @@ -70,6 +76,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """This endpoint lists the routable VirtualModels in the requested workspace and returns them in OpenAI's list models format. Each model ID is the VirtualModel @@ -79,6 +86,7 @@ def list_models( VirtualModels scoped to the request workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -108,4 +116,5 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/models.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/models.py index 30cb7eb033..8b51b07995 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/models.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/models.py @@ -12,13 +12,19 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="models", help="Manage models") @@ -70,6 +76,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """This endpoint lists the routable VirtualModels in the requested workspace and returns them in OpenAI's list models format. Each model ID is the VirtualModel @@ -79,6 +86,7 @@ def list_models( VirtualModels scoped to the request workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -108,4 +116,5 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/prompts.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/prompts.py index 523a88a1c6..ab88611bc9 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/prompts.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/prompts.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="prompts", help="Manage prompts") @@ -192,11 +198,13 @@ def list_prompts( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List prompts for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -241,6 +249,7 @@ def list_prompts( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py index 44c6e126df..2a7f3c141c 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="providers", help="Manage providers") @@ -261,11 +267,13 @@ def list_providers( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List model providers for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -317,6 +325,7 @@ def list_providers( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/virtual_models.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/virtual_models.py index 8546c1b29d..3e1b019c6b 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/virtual_models.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/virtual_models.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="virtual_models", help="Manage virtual_models") @@ -239,6 +245,7 @@ def list_virtual_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List VirtualModels for the given workspace. @@ -246,6 +253,7 @@ def list_virtual_models( Use `workspace=-` to list across all workspaces accessible to the caller.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -295,6 +303,7 @@ def list_virtual_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/annotations.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/annotations.py index edd6853ff0..1abd943cb7 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/annotations.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/annotations.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="annotations", help="Manage annotations") @@ -173,11 +179,13 @@ def list_annotations( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Annotations""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -228,6 +236,7 @@ def list_annotations( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/evaluator_results.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/evaluator_results.py index 6ccd4f4398..ec2cd715f4 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/evaluator_results.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/evaluator_results.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="evaluator_results", help="Manage evaluator_results") @@ -177,11 +183,13 @@ def list_evaluator_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Evaluator Results""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -231,6 +239,7 @@ def list_evaluator_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/__init__.py index 3a66134c47..da2b2392f7 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/__init__.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.types import ( @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_evaluator_results = _importlib_import_module( @@ -103,11 +109,13 @@ def list_spans( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Spans""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -172,6 +180,7 @@ def list_spans( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/evaluator_results.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/evaluator_results.py index da17494ec3..0231070e8d 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/evaluator_results.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/evaluator_results.py @@ -12,9 +12,19 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app -from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform_ext.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app(name="evaluator_results", help="Manage evaluator_results") @@ -29,10 +39,12 @@ def list_evaluator_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List Evaluator Results For Span""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -62,4 +74,5 @@ def list_evaluator_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/groups.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/groups.py index a38efe6d80..04c92555d5 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/groups.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/spans/groups.py @@ -12,10 +12,20 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages -from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform_ext.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app(name="groups", help="Manage groups") @@ -83,11 +93,13 @@ def list_groups( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Span Groups""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -152,6 +164,7 @@ def list_groups( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/traces.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/traces.py index 0cdf7b8f81..55f6a14220 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/traces.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/traces.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="traces", help="Manage traces") @@ -64,11 +70,13 @@ def list_traces( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Traces""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -119,6 +127,7 @@ def list_traces( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/__init__.py index a389bdf0f0..e0e6cfb653 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/__init__.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_results = _importlib_import_module("nemo_platform_ext.cli.commands.api.jobs.results") @@ -200,11 +206,13 @@ def get_logs_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """Get paginated logs for a platform job.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -247,6 +255,7 @@ def get_logs_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -318,11 +327,13 @@ def list_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List platform jobs with filtering and pagination.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -373,6 +384,7 @@ def list_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -386,10 +398,12 @@ def list_execution_profiles_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """Get all currently configured execution profiles.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -418,6 +432,7 @@ def list_execution_profiles_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/results.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/results.py index d9357d3a11..1fa798631a 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/results.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/results.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields from nemo_platform_ext.cli.core.types import ( @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="results", help="Manage results") @@ -141,10 +147,12 @@ def list_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List results for a job.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -175,6 +183,7 @@ def list_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/steps.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/steps.py index 32c04789e8..152076c1dc 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/steps.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/steps.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="steps", help="Manage steps") @@ -55,11 +61,13 @@ def list_steps( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List job steps with pagination and filtering.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -102,6 +110,7 @@ def list_steps( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/tasks.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/tasks.py index a8044c2482..6a63bb3e99 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/tasks.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/jobs/tasks.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields from nemo_platform_ext.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="tasks", help="Manage tasks") @@ -138,10 +144,12 @@ def list_tasks( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List tasks for a job step.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -172,6 +180,7 @@ def list_tasks( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/models/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/models/__init__.py index 355c40235f..de125ebcee 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/models/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/models/__init__.py @@ -13,7 +13,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_adapters = _importlib_import_module("nemo_platform_ext.cli.commands.api.models.adapters") @@ -290,6 +296,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Models endpoint with filtering, pagination, and sorting. @@ -299,6 +306,7 @@ def list_models( parameter.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -353,6 +361,7 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py index 073d10a358..3ab7d94c94 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="projects", help="Manage projects") @@ -166,6 +172,7 @@ def list_projects( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all projects in a workspace with pagination. @@ -183,6 +190,7 @@ def list_projects( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -225,6 +233,7 @@ def list_projects( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/secrets/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/secrets/__init__.py index c77a67b156..9be59ad24b 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/secrets/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/secrets/__init__.py @@ -14,7 +14,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import ( @@ -26,6 +31,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_admin = _importlib_import_module("nemo_platform_ext.cli.commands.api.secrets.admin") @@ -173,11 +179,13 @@ def list_secrets( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List available secrets""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -218,6 +226,7 @@ def list_secrets( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py index 30d201254d..36b8f60d45 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py @@ -14,7 +14,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -24,6 +29,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_members = _importlib_import_module("nemo_platform_ext.cli.commands.api.workspaces.members") @@ -185,6 +191,7 @@ def list_workspaces( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all workspaces with pagination. @@ -205,6 +212,7 @@ def list_workspaces( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -246,6 +254,7 @@ def list_workspaces( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/members.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/members.py index ac0466a2eb..0d9ab9b534 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/members.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/members.py @@ -12,7 +12,12 @@ from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields from nemo_platform_ext.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="members", help="Manage members") @@ -167,6 +173,7 @@ def list_members( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all members of a workspace with their roles. @@ -179,6 +186,7 @@ def list_members( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -209,6 +217,7 @@ def list_members( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/plugins.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/plugins.py index e6381334c1..f335ad3fc5 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/plugins.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/plugins.py @@ -9,9 +9,19 @@ from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app -from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform_ext.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app( name="plugins", @@ -38,6 +48,7 @@ def list_plugins( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List installed plugins. @@ -50,6 +61,7 @@ def list_plugins( state: CLIContext = ctx.obj columns_explicit = columns is not None and str(columns).strip() != "default" output_format = state.get_output_format(output_format, apply_non_tty_default=not columns_explicit) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -58,8 +70,9 @@ def list_plugins( Column("version", None), Column("description", None), ] + output_columns: str | list[Column] | None = columns if not columns_explicit: - columns = default_columns + output_columns = default_columns try: from nemo_platform_plugin.discovery import discover_manifests @@ -81,7 +94,8 @@ def list_plugins( items, is_list=True, output_format=output_format, - output_columns=columns, + output_columns=output_columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/skills/cli.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/skills/cli.py index 8dfb90dc7d..169ca5a2c6 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/skills/cli.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/skills/cli.py @@ -16,9 +16,19 @@ load_skills, ) from nemo_platform_ext.cli.core.context import CLIContext -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform_ext.cli.core.help_formatter import create_typer_app -from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform_ext.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) _AGENT_NAMES = ", ".join(list_agent_names()) @@ -125,6 +135,7 @@ def list_skills( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, source: Annotated[ list[str] | None, typer.Option( @@ -154,6 +165,7 @@ def list_skills( state: CLIContext = ctx.obj columns_explicit = columns is not None and str(columns).strip() != "default" output_format = state.get_output_format(output_format, apply_non_tty_default=not columns_explicit) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) default_columns = [ @@ -162,8 +174,9 @@ def list_skills( Column("source", "Source"), Column("description", "Description"), ] + output_columns: str | list[Column] | None = columns if not columns_explicit: - columns = default_columns + output_columns = default_columns try: skills = load_skills() @@ -195,10 +208,11 @@ def list_skills( items, is_list=True, output_format=output_format, - output_columns=columns, + output_columns=output_columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), wrap=True, + stream=stream, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/errors.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/errors.py index b05b1dbc03..ce0b157112 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/errors.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/errors.py @@ -16,6 +16,8 @@ if typing.TYPE_CHECKING: from nemo_platform import APIError +REMOTE_ERROR_EXIT_CODE = 3 + class MissingRequiredFieldsError(Exception): """Raised when required fields are missing from CLI input.""" @@ -148,7 +150,7 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None """ Handle all CLI errors and exit with appropriate error code. - Exit codes: 0 = Success, 1 = General error, 2 = Usage error + Exit codes: 0 = Success, 1 = Local/unexpected error, 2 = Usage error, 3 = Remote/API error """ from rich.console import Console @@ -239,33 +241,33 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None "[yellow]Hint:[/] Run [cyan]'nemo auth login'[/] or set the token manually " "with [cyan]nemo config set --access-token [/], or use [cyan]NMP_ACCESS_TOKEN[/]." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, PermissionDeniedError): console.print(f"[bold red]Permission denied:[/] ({error.status_code}) {_format_api_error(error)}") console.print( "[yellow]Hint:[/] Your current credentials do not have access to perform this operation. " "Contact your administrator to request access." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, NotFoundError): console.print(f"[bold red]Not found:[/] ({error.status_code}) {_format_api_error(error)}") _print_api_request_context(console, error) console.print(f"[yellow]Hint:[/] {_format_not_found_hint(ctx, prog)}") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, BadRequestError): console.print(f"[bold red]Bad request:[/] ({error.status_code}) {_format_api_error(error)}") console.print("[yellow]Hint:[/] Check your input values. Run with [cyan]--help[/] to see required options.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, ConflictError): console.print(f"[bold red]Conflict:[/] ({error.status_code}) {_format_api_error(error)}") console.print( "[yellow]Hint:[/] A resource with this name already exists. Try a different name or delete the existing one." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, RateLimitError): console.print(f"[bold red]Rate limit exceeded:[/] ({error.status_code}) {_format_api_error(error)}") console.print("[yellow]Hint:[/] Too many requests. Wait a moment and try again.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, InternalServerError): formatted = _format_api_error(error) console.print(f"[bold red]Server error:[/] ({error.status_code}) {formatted}") @@ -274,27 +276,27 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None console.print(f"[yellow]Hint:[/] {_format_not_found_hint(ctx, prog)}") else: console.print("[yellow]Hint:[/] This is a server-side issue. Try again later or contact support.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) + elif isinstance(error, APITimeoutError): + console.print(f"[bold red]Timeout error:[/] {_format_api_error(error)}") + _print_api_request_context(console, error) + console.print("[yellow]Hint:[/] The request timed out. The server may be busy - try again later.") + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIConnectionError): console.print(f"[bold red]Connection error:[/] {_format_api_error(error)}") _print_api_request_context(console, error) console.print( "[yellow]Hint:[/] Check your network connection and verify that [cyan]base-url[/] you configured is correct." ) - raise typer.Exit(code=1) - elif isinstance(error, APITimeoutError): - console.print(f"[bold red]Timeout error:[/] {_format_api_error(error)}") - _print_api_request_context(console, error) - console.print("[yellow]Hint:[/] The request timed out. The server may be busy - try again later.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIStatusError): console.print(f"[bold red]API error:[/] ({error.status_code}) {_format_api_error(error)}") _print_api_request_context(console, error) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIError): console.print(f"[bold red]API error:[/] {_format_api_error(error)}") _print_api_request_context(console, error) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, ValueError) and "Missing workspace argument" in str(error): console.print("[bold red]Missing workspace:[/] No workspace configured for this command.") console.print( diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/formatters.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/formatters.py index 32ae0f19b2..31360500dd 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/formatters.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/formatters.py @@ -10,9 +10,11 @@ import json import re import sys +from collections.abc import Iterator from dataclasses import dataclass -from typing import Any, Literal +from typing import Any +import click import yaml from rich.console import Console from rich.syntax import Syntax @@ -86,17 +88,29 @@ def _extract_items_from_response(data: Any) -> list[Any]: if isinstance(data, list): return data + return list(_iter_items_from_response(data)) + + +def _iter_items_from_response(data: Any) -> Iterator[Any]: + """Iterate list items from an API response without materializing callables.""" + if isinstance(data, list): + yield from data + return + for field in LIST_ITEM_FIELDS: if isinstance(data, dict): if field in data: - return data[field] + items = data[field] + else: + continue else: - value = getattr(data, field, None) - if value is None: + items = getattr(data, field, None) + if items is None: continue - return list(value()) if callable(value) else value + items = items() if callable(items) else items - return [] + yield from items + return def _to_dict_items(items: list[Any]) -> list[dict[str, Any]]: @@ -263,6 +277,19 @@ def format_json( return json_str +def iter_json_lines(data: Any, *, is_list: bool = False) -> Iterator[str]: + """Return newline-delimited JSON records for streaming-style output.""" + records = _iter_items_from_response(data) if is_list else iter((data,)) + for record in records: + yield json.dumps(model_to_dict(record), ensure_ascii=False, separators=(",", ":")) + + +def validate_stream_output_format(output_format: str | None, stream: bool) -> None: + """Validate output format compatibility before streaming data acquisition starts.""" + if stream and output_format not in {"json", "raw"}: + raise click.UsageError("--stream requires --output json or --output raw.") + + def format_yaml( data: Any, syntax_highlight: bool = True, @@ -571,12 +598,13 @@ def format_output( *, is_list: bool = False, output_format: str | None = None, - output_columns: Literal["all"] | list[Column] | None = None, + output_columns: str | list[Column] | None = None, indent: int = 2, no_truncate: bool | None = None, timestamp_format: str | None = None, wrap: bool = False, wrap_max_width: int | None = 90, + stream: bool = False, ) -> None: """ Format and print output to stdout. @@ -601,9 +629,20 @@ def format_output( uses the full terminal width. wrap_max_width: Per-column character cap for wrapped columns when `wrap=True` and `no_truncate=False`. Defaults to 90. + stream: Emit newline-delimited JSON records. List responses emit one + record per item; entity responses emit one record. """ from nemo_platform_ext.cli.core.table_config import resolve_and_validate_columns, validate_output_columns + timestamp_format = timestamp_format or "iso" + + if stream: + validate_stream_output_format(output_format, stream) + for line in iter_json_lines(data, is_list=is_list): + click.echo(line) + sys.stdout.flush() + return + if not is_list and output_format in {"table", "markdown", "csv"}: output_format = "json" # Fallback to JSON for single items in these formats @@ -634,7 +673,7 @@ def format_output( if ( output_format == "table" and not truncate - and output_columns + and isinstance(output_columns, list) and len(output_columns) > MAX_TABLE_COLUMNS_WITHOUT_TRUNCATE ): print( @@ -653,6 +692,7 @@ def format_output( output = format_yaml(data, syntax_highlight=True, background=False) print(output) elif output_format == "table": + assert isinstance(output_columns, list) # Table format. When wrapping is on and --no-truncate is set, drop the # per-column cap so wrapping uses the full terminal width. effective_wrap_max_width = None if (wrap and not truncate) else wrap_max_width @@ -666,12 +706,14 @@ def format_output( ) print(output) elif output_format == "markdown": + assert isinstance(output_columns, list) # Markdown table format output = format_markdown_table( data, columns=output_columns, truncate=truncate, timestamp_format=timestamp_format ) print(output) elif output_format == "csv": + assert isinstance(output_columns, list) # CSV format output = format_csv(data, columns=output_columns, truncate=truncate, timestamp_format=timestamp_format) print(output, end="") # CSV already includes newlines diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/types.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/types.py index 526465f558..acd650fe46 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/types.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/types.py @@ -26,6 +26,7 @@ ListOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for the list of results.", @@ -37,6 +38,7 @@ EntityOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for an entity.", @@ -47,6 +49,7 @@ ConfigOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for config.", @@ -54,6 +57,15 @@ ), ] +StreamOutputOption = Annotated[ + bool, + typer.Option( + "--stream", + help="Emit newline-delimited JSON, one record per line. Requires JSON or raw output.", + rich_help_panel="Output Options", + ), +] + NoTruncateOption = Annotated[ bool | None, typer.Option( diff --git a/packages/nemo_platform_ext/tests/cli/commands/skills/test_cli.py b/packages/nemo_platform_ext/tests/cli/commands/skills/test_cli.py index 43bcd1f929..3ae0cf01f0 100644 --- a/packages/nemo_platform_ext/tests/cli/commands/skills/test_cli.py +++ b/packages/nemo_platform_ext/tests/cli/commands/skills/test_cli.py @@ -4,6 +4,7 @@ """Tests for the skills CLI commands.""" from pathlib import Path +from unittest.mock import patch import pytest from nemo_platform_ext.cli.app import app @@ -21,7 +22,11 @@ def _example_plugin_skills_root() -> Path: - return Path(__file__).resolve().parents[6] / "plugins" / "example-plugin" / "src" / "nemo_example_plugin" / "skills" + relative_path = Path("plugins/example-plugin/src/nemo_example_plugin/skills") + for parent in Path(__file__).resolve().parents: + if (parent / relative_path).is_dir(): + return parent / relative_path + raise FileNotFoundError(f"Could not find {relative_path} from {__file__}") def _platform_skill_names() -> set[str]: @@ -100,6 +105,14 @@ def test_list_duplicate_skill_error_is_clean(self, monkeypatch): assert_exit_code(result, 1) assert "Error: duplicate skill" in result.output + def test_stream_requires_json_output_before_loading_skills(self): + with patch("nemo_platform_ext.cli.commands.skills.cli.load_skills") as mock_load_skills: + result = runner.invoke(app, "skills list --output table --stream") + + assert_exit_code(result, 2) + assert "--stream requires --output json or --output raw" in result.stderr + mock_load_skills.assert_not_called() + def test_list_source_column_shows_distribution_names(self, example_plugin_skills): """JSON output is stable and easy to assert against the new ``Source`` semantics. diff --git a/packages/nemo_platform_ext/tests/cli/commands/test_plugins.py b/packages/nemo_platform_ext/tests/cli/commands/test_plugins.py index a7442aa983..64c87b7bfe 100644 --- a/packages/nemo_platform_ext/tests/cli/commands/test_plugins.py +++ b/packages/nemo_platform_ext/tests/cli/commands/test_plugins.py @@ -43,6 +43,8 @@ def test_plugins_list_help(self): assert_exit_code(result, 0) assert "List installed plugins." in result.stdout assert "--output-format" in result.stdout + assert "--output" in result.stdout + assert "--stream" in result.stdout class TestPluginsList: @@ -88,6 +90,40 @@ def test_list_json_output(self): assert '"version": "0.1.0"' in result.stdout assert '"description": "Example plugin"' in result.stdout + def test_list_accepts_output_alias(self): + manifests = { + "example": PluginManifest(name="example", version="0.1.0", description="Example plugin"), + } + + with patch("nemo_platform_plugin.discovery.discover_manifests", return_value=manifests): + result = _invoke("plugins", "list", "--output", "json") + + assert_exit_code(result, 0) + assert '"name": "example"' in result.stdout + + def test_list_streams_json_lines(self): + manifests = { + "beta": PluginManifest(name="beta", version="1.5.0", description="Beta plugin"), + "alpha": PluginManifest(name="alpha", version="1.0.0", description="Alpha plugin"), + } + + with patch("nemo_platform_plugin.discovery.discover_manifests", return_value=manifests): + result = _invoke("plugins", "list", "--output", "json", "--stream") + + assert_exit_code(result, 0) + assert result.stdout.splitlines() == [ + '{"name":"alpha","version":"1.0.0","description":"Alpha plugin"}', + '{"name":"beta","version":"1.5.0","description":"Beta plugin"}', + ] + + def test_stream_requires_json_output(self): + with patch("nemo_platform_plugin.discovery.discover_manifests") as mock_discover: + result = _invoke("plugins", "list", "--output", "table", "--stream") + + assert_exit_code(result, 2) + assert "--stream requires --output json or --output raw" in result.stderr + mock_discover.assert_not_called() + def test_list_empty_result(self): with patch("nemo_platform_plugin.discovery.discover_manifests", return_value={}): result = _invoke("plugins", "list", "-f", "json") diff --git a/packages/nemo_platform_ext/tests/cli/commands/use_cases/test_chat.py b/packages/nemo_platform_ext/tests/cli/commands/use_cases/test_chat.py index 6fd73d80ea..bc6a4b1e31 100644 --- a/packages/nemo_platform_ext/tests/cli/commands/use_cases/test_chat.py +++ b/packages/nemo_platform_ext/tests/cli/commands/use_cases/test_chat.py @@ -29,6 +29,8 @@ from nemo_platform_ext.cli.commands.use_cases.chat import _parse_model_and_workspace from typer.testing import CliRunner +REMOTE_ERROR_EXIT_CODE = 3 + # ============================================================================= # Unit Tests for _parse_model_and_workspace # ============================================================================= @@ -80,7 +82,7 @@ def test_parse_model_and_workspace_no_workspace_error() -> None: # CLI Syntax Validation Tests # # These tests verify the CLI accepts valid syntax. They run against a fake -# server URL so commands fail with connection errors (exit code 1), not +# server URL so commands fail with connection errors (exit code 3), not # syntax errors (exit code 2). # ============================================================================= @@ -139,20 +141,19 @@ def test_chat_missing_model_fails_with_usage_error(runner: CliRunner) -> None: def test_chat_model_only(runner: CliRunner) -> None: """Chat with just model argument parses correctly.""" result = runner.invoke(app, ["chat", "my-model", "hello"]) - # Exit code 1 = runtime error (connection), not 2 = syntax error - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_model_with_inline_workspace(runner: CliRunner) -> None: """Chat with workspace/model syntax parses correctly.""" result = runner.invoke(app, ["chat", "my-workspace/my-model", "hello"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_workspace_flag(runner: CliRunner) -> None: """Chat with --workspace flag parses correctly.""" result = runner.invoke(app, ["chat", "my-model", "hello", "--workspace", "test-ws"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_provider_flag(runner: CliRunner) -> None: @@ -160,7 +161,7 @@ def test_chat_with_provider_flag(runner: CliRunner) -> None: result = runner.invoke( app, ["chat", "nvidia/model-id", "hello", "--provider", "nvidia-build", "--workspace", "test-ws"] ) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_provider_rejects_workspace_prefix(runner: CliRunner) -> None: @@ -177,19 +178,19 @@ def test_chat_provider_rejects_workspace_prefix(runner: CliRunner) -> None: def test_chat_with_temperature(runner: CliRunner) -> None: """Chat with --temperature parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--temperature", "0.7"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_max_tokens(runner: CliRunner) -> None: """Chat with --max-tokens parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--max-tokens", "512"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_system_message(runner: CliRunner) -> None: """Chat with --system-message parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--system-message", "You are helpful."]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_all_options(runner: CliRunner) -> None: @@ -212,7 +213,7 @@ def test_chat_all_options(runner: CliRunner) -> None: "Be concise.", ], ) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_temperature_rejects_non_numeric(runner: CliRunner) -> None: diff --git a/packages/nemo_platform_ext/tests/cli/core/test_errors.py b/packages/nemo_platform_ext/tests/cli/core/test_errors.py index d6e12abaf3..5d8c044717 100644 --- a/packages/nemo_platform_ext/tests/cli/core/test_errors.py +++ b/packages/nemo_platform_ext/tests/cli/core/test_errors.py @@ -30,6 +30,8 @@ ) from typer.testing import CliRunner +DOCUMENTED_REMOTE_ERROR_EXIT_CODE = 3 + @pytest.mark.parametrize( "body,message,expected", @@ -78,7 +80,7 @@ def test_handle_api_status_errors(capsys, error_class, status_code, expected_pre with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert expected_prefix in captured.err assert f"({status_code})" in captured.err @@ -92,7 +94,7 @@ def test_handle_api_connection_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Connection error:" in captured.err assert "base-url" in captured.err @@ -105,9 +107,9 @@ def test_handle_api_timeout_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() - assert "Connection error:" in captured.err + assert "Timeout error:" in captured.err assert "timed out" in captured.err @@ -119,7 +121,7 @@ def test_handle_api_status_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err assert "(418)" in captured.err @@ -133,7 +135,7 @@ def test_handle_api_status_error_prints_request_context(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err assert "Request: POST http://test/apis/models/v2/workspaces/default/models" in captured.err @@ -147,7 +149,7 @@ def test_handle_generic_api_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err @@ -281,7 +283,7 @@ def failing_function(): with pytest.raises(typer.Exit) as exc_info: failing_function() - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err @@ -295,7 +297,7 @@ def test_not_found_prints_server_message(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err assert "Workspace 'default' not found" in captured.err @@ -309,7 +311,7 @@ def test_not_found_prints_request_context_and_404_hint(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Request: GET http://test/apis/agents/v2/workspaces/default/agents" in captured.err assert "Target: agents API route /apis/agents/v2/workspaces/default/agents" in captured.err @@ -327,7 +329,7 @@ def test_not_found_hint_fallback_when_no_ctx(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err assert "Check the resource name/ID" in captured.err diff --git a/packages/nemo_platform_ext/tests/cli/core/test_formatters.py b/packages/nemo_platform_ext/tests/cli/core/test_formatters.py index f3e90b09ac..a3a1b64881 100644 --- a/packages/nemo_platform_ext/tests/cli/core/test_formatters.py +++ b/packages/nemo_platform_ext/tests/cli/core/test_formatters.py @@ -7,6 +7,7 @@ import re from unittest.mock import Mock, patch +import click import pytest import yaml from nemo_platform_ext.cli.core.formatters import ( @@ -19,7 +20,9 @@ format_stream_event, format_table, format_yaml, + iter_json_lines, model_to_dict, + validate_stream_output_format, ) @@ -100,6 +103,67 @@ def test_format_json_nested_data(): assert parsed == data +def test_iter_json_lines_extracts_list_items(): + """JSON Lines output should emit one compact JSON object per list item.""" + mock_response = Mock() + mock_response.data = [ + {"id": "1", "name": "Alice"}, + {"id": "2", "name": "Bob"}, + ] + + assert list(iter_json_lines(mock_response, is_list=True)) == [ + '{"id":"1","name":"Alice"}', + '{"id":"2","name":"Bob"}', + ] + + +def test_iter_json_lines_keeps_callable_items_lazy(): + """Callable response item sources should stream without list materialization.""" + + class LazyResponse: + def __init__(self) -> None: + self.yielded = 0 + + def items(self): + for item in [{"id": "1"}, {"id": "2"}]: + self.yielded += 1 + yield item + + response = LazyResponse() + lines = iter_json_lines(response, is_list=True) + + assert response.yielded == 0 + assert next(lines) == '{"id":"1"}' + assert response.yielded == 1 + assert next(lines) == '{"id":"2"}' + assert response.yielded == 2 + with pytest.raises(StopIteration): + next(lines) + + +def test_format_output_streams_json_lines(capsys): + """format_output should print list items as newline-delimited JSON when streaming.""" + mock_response = Mock() + mock_response.data = [{"id": "1"}, {"id": "2"}] + + format_output(mock_response, is_list=True, output_format="json", stream=True) + + captured = capsys.readouterr() + assert captured.out.splitlines() == ['{"id":"1"}', '{"id":"2"}'] + + +def test_format_output_stream_requires_json_compatible_format(): + """--stream is only valid for JSON-compatible formats.""" + with pytest.raises(click.UsageError, match="--stream requires --output json or --output raw"): + format_output([{"id": "1"}], is_list=True, output_format="table", stream=True) + + +def test_validate_stream_output_format_rejects_table_output(): + """Command handlers can fail before loading API or discovery data.""" + with pytest.raises(click.UsageError, match="--stream requires --output json or --output raw"): + validate_stream_output_format("table", True) + + @patch("nemo_platform_ext.cli.core.formatters.is_tty") def test_format_yaml_no_highlighting(mock_is_tty): """Test YAML formatting without syntax highlighting.""" diff --git a/packages/nemo_platform_ext/tests/cli/test_app.py b/packages/nemo_platform_ext/tests/cli/test_app.py index 91194e7cb3..13053884b9 100644 --- a/packages/nemo_platform_ext/tests/cli/test_app.py +++ b/packages/nemo_platform_ext/tests/cli/test_app.py @@ -52,13 +52,43 @@ def test_help_includes_getting_started(): assert "Getting started:" in result.stdout assert "nemo docs --list" in result.stdout assert "nemo services run --help" in result.stdout + assert "Exit codes:" in result.stdout + assert "3: Remote/API error" in result.stdout # Help panel truncation depends on terminal width; match a stable prefix. assert "Set up NeMo Platform" in result.stdout + assert "--output-format, --output, -f" in result.stdout assert "--help, -h" in result.stdout assert "nemo auth login --base-url" not in result.stdout assert "nemo quickstart configure" not in result.stdout +def test_generated_list_help_includes_stream_option(): + runner = CliRunner() + qs_config = QuickstartConfig(auth_enabled=False) + + with patch("nemo_platform_ext.quickstart.QuickstartConfig.load", return_value=qs_config): + result = runner.invoke(app, ["models", "list", "--help"]) + + assert result.exit_code == 0 + assert "--stream" in result.stdout + assert "--output-format, --output, -f" in result.stdout + + +def test_generated_list_validates_stream_output_before_client_setup(): + runner = CliRunner() + qs_config = QuickstartConfig(auth_enabled=False) + + with ( + patch("nemo_platform_ext.quickstart.QuickstartConfig.load", return_value=qs_config), + patch("nemo_platform_ext.cli.core.context.CLIContext.get_client") as mock_get_client, + ): + result = runner.invoke(app, ["workspaces", "list", "--output", "table", "--stream"]) + + assert result.exit_code == 2 + assert "--stream requires --output json or --output raw" in result.stderr + mock_get_client.assert_not_called() + + @pytest.mark.parametrize( ("argv", "expected_text"), [ diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/app.py b/sdk/python/nemo-platform/src/nemo_platform/cli/app.py index 16a9fefcb2..1b1c852c4a 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/app.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/app.py @@ -7,7 +7,7 @@ import logging from importlib.metadata import EntryPoint -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING, Annotated, cast import typer @@ -24,6 +24,7 @@ TopLevelEntry, build_top_level_entries, ) +from nemo_platform.config.types import OutputFormat as ConfigOutputFormat if TYPE_CHECKING: from nemo_platform_plugin.cli import NemoCLI @@ -173,6 +174,7 @@ def main( ListOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", help="Output format for how results are printed.", rich_help_panel="Global Options", @@ -242,6 +244,12 @@ def main( [green]Examples:[/] nemo workspaces list --output-format markdown nemo workspaces get default -f json + + [green]Exit codes:[/] + - 0: Success + - 1: Local or unexpected error + - 2: Command usage error + - 3: Remote/API error """ # Lazy imports for performance (avoid loading pydantic_settings for --help) from nemo_platform.cli.core.context import CLIContext @@ -274,7 +282,7 @@ def main( if base_url is not None: overrides["base_url"] = base_url if output_format is not None: - overrides["output_format"] = output_format + overrides["output_format"] = cast(ConfigOutputFormat, output_format) elif agent_mode: overrides["output_format"] = "markdown" if timestamp_format is not None: diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/adapters.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/adapters.py index 64233d99fb..9c7e816229 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/adapters.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/adapters.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="adapters", help="Manage adapters") @@ -224,11 +230,13 @@ def list_adapters( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Adapters""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -279,6 +287,7 @@ def list_adapters( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/experiments.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/experiments.py index b5cab61b3a..f40fb9f7eb 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/experiments.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/experiments.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="experiments", help="Manage experiments") @@ -191,11 +197,13 @@ def list_experiments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Experiments""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -238,6 +246,7 @@ def list_experiments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/files/filesets.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/files/filesets.py index e2b79efeba..170fc375fc 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/files/filesets.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/files/filesets.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="filesets", help="Manage filesets") @@ -210,6 +216,7 @@ def list_filesets( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Filesets endpoint with filtering and pagination. @@ -218,6 +225,7 @@ def list_filesets( updated_at via query parameters. Returns paginated results with sorting options.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -266,6 +274,7 @@ def list_filesets( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/guardrail/configs.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/guardrail/configs.py index 905b2d26e4..1c07f02e1e 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/guardrail/configs.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/guardrail/configs.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="configs", help="Manage configs") @@ -160,6 +166,7 @@ def list_configs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List available guardrail configs. @@ -167,6 +174,7 @@ def list_configs( Lists guardrail configs for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -209,6 +217,7 @@ def list_configs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/iam/role_bindings.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/iam/role_bindings.py index 3147f3e263..6f697bdb38 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/iam/role_bindings.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/iam/role_bindings.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="role_bindings", help="Manage role_bindings") @@ -178,11 +184,13 @@ def list_role_bindings( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all role bindings (Platform Admin only)""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -231,6 +239,7 @@ def list_role_bindings( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/__init__.py index e62071a4fd..ece684ac76 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/__init__.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_versions = _importlib_import_module( @@ -221,6 +227,7 @@ def list_deployment_configs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List ModelDeploymentConfigs for a specific workspace. @@ -229,6 +236,7 @@ def list_deployment_configs( version of each config.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -278,6 +286,7 @@ def list_deployment_configs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/versions.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/versions.py index 2553ae97f0..bf80b2b967 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/versions.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployment_configs/versions.py @@ -12,13 +12,19 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="versions", help="Manage versions") @@ -61,10 +67,12 @@ def list_versions( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all versions of a ModelDeploymentConfig.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -94,6 +102,7 @@ def list_versions( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/__init__.py index ef3ba6c879..790663ae7b 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/__init__.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -23,6 +28,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) from nemo_platform.cli.core.waiters import wait_for_inference_deployment @@ -250,6 +256,7 @@ def list_deployments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List ModelDeployments for a specific workspace. @@ -257,6 +264,7 @@ def list_deployments( By default, returns only the latest version of each deployment.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -309,6 +317,7 @@ def list_deployments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -324,6 +333,7 @@ def list_models_deployments( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """Get Latest ModelDeployment's Model Entities from Entity Store. @@ -334,6 +344,7 @@ def list_models_deployments( TODO: Implement model entity retrieval based on deployment config.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -363,6 +374,7 @@ def list_models_deployments( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/versions.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/versions.py index acee6d3a15..68d51f00a8 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/versions.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/deployments/versions.py @@ -12,13 +12,19 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="versions", help="Manage versions") @@ -72,10 +78,12 @@ def list_versions( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all versions of a ModelDeployment.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -105,6 +113,7 @@ def list_versions( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/gateway/openai/v1/models.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/gateway/openai/v1/models.py index f2d32541ed..41dd9b0335 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/gateway/openai/v1/models.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/gateway/openai/v1/models.py @@ -12,13 +12,19 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="models", help="Manage models") @@ -70,6 +76,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """This endpoint lists the routable VirtualModels in the requested workspace and returns them in OpenAI's list models format. Each model ID is the VirtualModel @@ -79,6 +86,7 @@ def list_models( VirtualModels scoped to the request workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -108,4 +116,5 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/models.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/models.py index 182204052b..d6c48c806b 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/models.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/models.py @@ -12,13 +12,19 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.types import ( EntityOutputFormatOption, ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="models", help="Manage models") @@ -70,6 +76,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """This endpoint lists the routable VirtualModels in the requested workspace and returns them in OpenAI's list models format. Each model ID is the VirtualModel @@ -79,6 +86,7 @@ def list_models( VirtualModels scoped to the request workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -108,4 +116,5 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/prompts.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/prompts.py index 287ee600eb..58afb99c5b 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/prompts.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/prompts.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="prompts", help="Manage prompts") @@ -192,11 +198,13 @@ def list_prompts( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List prompts for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -241,6 +249,7 @@ def list_prompts( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/providers.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/providers.py index 3fe49e4c57..af334ade62 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/providers.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/providers.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="providers", help="Manage providers") @@ -261,11 +267,13 @@ def list_providers( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List model providers for a specific workspace.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -317,6 +325,7 @@ def list_providers( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/virtual_models.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/virtual_models.py index 7536b725fb..8105f8a3d3 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/virtual_models.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/inference/virtual_models.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="virtual_models", help="Manage virtual_models") @@ -239,6 +245,7 @@ def list_virtual_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List VirtualModels for the given workspace. @@ -246,6 +253,7 @@ def list_virtual_models( Use `workspace=-` to list across all workspaces accessible to the caller.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -295,6 +303,7 @@ def list_virtual_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/annotations.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/annotations.py index ef4e25af77..23a2ee20c0 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/annotations.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/annotations.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="annotations", help="Manage annotations") @@ -173,11 +179,13 @@ def list_annotations( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Annotations""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -228,6 +236,7 @@ def list_annotations( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/evaluator_results.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/evaluator_results.py index ef7dfc11e9..9de297dc75 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/evaluator_results.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/evaluator_results.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="evaluator_results", help="Manage evaluator_results") @@ -177,11 +183,13 @@ def list_evaluator_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Evaluator Results""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -231,6 +239,7 @@ def list_evaluator_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/__init__.py index ebd8005b56..78b7bb8854 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/__init__.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.types import ( @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_evaluator_results = _importlib_import_module( @@ -103,11 +109,13 @@ def list_spans( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Spans""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -172,6 +180,7 @@ def list_spans( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/evaluator_results.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/evaluator_results.py index 38f80da591..1d37b6e309 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/evaluator_results.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/evaluator_results.py @@ -12,9 +12,19 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app -from nemo_platform.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app(name="evaluator_results", help="Manage evaluator_results") @@ -29,10 +39,12 @@ def list_evaluator_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List Evaluator Results For Span""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -62,4 +74,5 @@ def list_evaluator_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/groups.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/groups.py index a172532990..616a6dacca 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/groups.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/spans/groups.py @@ -12,10 +12,20 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages -from nemo_platform.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app(name="groups", help="Manage groups") @@ -83,11 +93,13 @@ def list_groups( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Span Groups""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -152,6 +164,7 @@ def list_groups( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/traces.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/traces.py index 961a2b7dae..11b1a8890a 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/traces.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/traces.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="traces", help="Manage traces") @@ -64,11 +70,13 @@ def list_traces( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Traces""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -119,6 +127,7 @@ def list_traces( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/__init__.py index 83e0c9fba3..9a6fcc0b41 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/__init__.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_results = _importlib_import_module("nemo_platform.cli.commands.api.jobs.results") @@ -200,11 +206,13 @@ def get_logs_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """Get paginated logs for a platform job.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -247,6 +255,7 @@ def get_logs_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -318,11 +327,13 @@ def list_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List platform jobs with filtering and pagination.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -373,6 +384,7 @@ def list_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) @@ -386,10 +398,12 @@ def list_execution_profiles_jobs( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """Get all currently configured execution profiles.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -418,6 +432,7 @@ def list_execution_profiles_jobs( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/results.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/results.py index 94b9330d9f..c1703c8d64 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/results.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/results.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields from nemo_platform.cli.core.types import ( @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="results", help="Manage results") @@ -141,10 +147,12 @@ def list_results( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List results for a job.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -175,6 +183,7 @@ def list_results( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/steps.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/steps.py index e6fbad5c7a..18d376eef6 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/steps.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/steps.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="steps", help="Manage steps") @@ -55,11 +61,13 @@ def list_steps( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List job steps with pagination and filtering.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -102,6 +110,7 @@ def list_steps( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/tasks.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/tasks.py index fd4b642b65..d6b6bcc74a 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/tasks.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/jobs/tasks.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields from nemo_platform.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="tasks", help="Manage tasks") @@ -138,10 +144,12 @@ def list_tasks( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List tasks for a job step.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -172,6 +180,7 @@ def list_tasks( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/models/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/models/__init__.py index e27ac75164..282f1d7e45 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/models/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/models/__init__.py @@ -13,7 +13,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, read_payload, validate_required_fields @@ -22,6 +27,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_adapters = _importlib_import_module("nemo_platform.cli.commands.api.models.adapters") @@ -290,6 +296,7 @@ def list_models( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List Models endpoint with filtering, pagination, and sorting. @@ -299,6 +306,7 @@ def list_models( parameter.""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -353,6 +361,7 @@ def list_models( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/projects.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/projects.py index 8e7116ce58..266b8a3517 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/projects.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/projects.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -21,6 +26,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="projects", help="Manage projects") @@ -166,6 +172,7 @@ def list_projects( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all projects in a workspace with pagination. @@ -183,6 +190,7 @@ def list_projects( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -225,6 +233,7 @@ def list_projects( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/secrets/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/secrets/__init__.py index ec210b9447..ceb7732bd7 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/secrets/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/secrets/__init__.py @@ -14,7 +14,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import ( @@ -26,6 +31,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_admin = _importlib_import_module("nemo_platform.cli.commands.api.secrets.admin") @@ -173,11 +179,13 @@ def list_secrets( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List available secrets""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -218,6 +226,7 @@ def list_secrets( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/__init__.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/__init__.py index 4572b95e68..02f0d2b364 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/__init__.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/__init__.py @@ -14,7 +14,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields @@ -24,6 +29,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) _cli_child_members = _importlib_import_module("nemo_platform.cli.commands.api.workspaces.members") @@ -185,6 +191,7 @@ def list_workspaces( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False, ) -> None: """List all workspaces with pagination. @@ -205,6 +212,7 @@ def list_workspaces( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -246,6 +254,7 @@ def list_workspaces( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) if not all_pages: warn_if_more_pages(items, pagination_type) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/members.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/members.py index bcac6b77d3..532b972942 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/members.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/workspaces/members.py @@ -12,7 +12,12 @@ from nemo_platform.cli.core.code_generator import handle_code_generation from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app from nemo_platform.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields from nemo_platform.cli.core.types import ( @@ -20,6 +25,7 @@ ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, + StreamOutputOption, ) app = create_typer_app(name="members", help="Manage members") @@ -167,6 +173,7 @@ def list_members( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List all members of a workspace with their roles. @@ -179,6 +186,7 @@ def list_members( ```""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -209,6 +217,7 @@ def list_members( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/plugins.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/plugins.py index 69c2cdb40e..10d75e29cd 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/plugins.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/plugins.py @@ -9,9 +9,19 @@ from nemo_platform.cli.core.context import CLIContext from nemo_platform.cli.core.errors import handle_errors -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import collect_warnings, create_typer_app -from nemo_platform.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) app = create_typer_app( name="plugins", @@ -38,6 +48,7 @@ def list_plugins( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, ) -> None: """List installed plugins. @@ -50,6 +61,7 @@ def list_plugins( state: CLIContext = ctx.obj columns_explicit = columns is not None and str(columns).strip() != "default" output_format = state.get_output_format(output_format, apply_non_tty_default=not columns_explicit) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -58,8 +70,9 @@ def list_plugins( Column("version", None), Column("description", None), ] + output_columns: str | list[Column] | None = columns if not columns_explicit: - columns = default_columns + output_columns = default_columns try: from nemo_platform_plugin.discovery import discover_manifests @@ -81,7 +94,8 @@ def list_plugins( items, is_list=True, output_format=output_format, - output_columns=columns, + output_columns=output_columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/skills/cli.py b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/skills/cli.py index c539cc1eed..a696f46ea8 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/commands/skills/cli.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/commands/skills/cli.py @@ -16,9 +16,19 @@ load_skills, ) from nemo_platform.cli.core.context import CLIContext -from nemo_platform.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform.cli.core.formatters import ( + Column, + check_output_columns_with_format, + format_output, + validate_stream_output_format, +) from nemo_platform.cli.core.help_formatter import create_typer_app -from nemo_platform.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform.cli.core.types import ( + ListOutputFormatOption, + NoTruncateOption, + OutputColumnsOption, + StreamOutputOption, +) _AGENT_NAMES = ", ".join(list_agent_names()) @@ -125,6 +135,7 @@ def list_skills( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, source: Annotated[ list[str] | None, typer.Option( @@ -154,6 +165,7 @@ def list_skills( state: CLIContext = ctx.obj columns_explicit = columns is not None and str(columns).strip() != "default" output_format = state.get_output_format(output_format, apply_non_tty_default=not columns_explicit) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) default_columns = [ @@ -162,8 +174,9 @@ def list_skills( Column("source", "Source"), Column("description", "Description"), ] + output_columns: str | list[Column] | None = columns if not columns_explicit: - columns = default_columns + output_columns = default_columns try: skills = load_skills() @@ -195,10 +208,11 @@ def list_skills( items, is_list=True, output_format=output_format, - output_columns=columns, + output_columns=output_columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), wrap=True, + stream=stream, ) diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/core/errors.py b/sdk/python/nemo-platform/src/nemo_platform/cli/core/errors.py index f290cf0e59..7b02834159 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/core/errors.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/core/errors.py @@ -16,6 +16,8 @@ if typing.TYPE_CHECKING: from nemo_platform import APIError +REMOTE_ERROR_EXIT_CODE = 3 + class MissingRequiredFieldsError(Exception): """Raised when required fields are missing from CLI input.""" @@ -148,7 +150,7 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None """ Handle all CLI errors and exit with appropriate error code. - Exit codes: 0 = Success, 1 = General error, 2 = Usage error + Exit codes: 0 = Success, 1 = Local/unexpected error, 2 = Usage error, 3 = Remote/API error """ from rich.console import Console @@ -239,33 +241,33 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None "[yellow]Hint:[/] Run [cyan]'nemo auth login'[/] or set the token manually " "with [cyan]nemo config set --access-token [/], or use [cyan]NMP_ACCESS_TOKEN[/]." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, PermissionDeniedError): console.print(f"[bold red]Permission denied:[/] ({error.status_code}) {_format_api_error(error)}") console.print( "[yellow]Hint:[/] Your current credentials do not have access to perform this operation. " "Contact your administrator to request access." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, NotFoundError): console.print(f"[bold red]Not found:[/] ({error.status_code}) {_format_api_error(error)}") _print_api_request_context(console, error) console.print(f"[yellow]Hint:[/] {_format_not_found_hint(ctx, prog)}") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, BadRequestError): console.print(f"[bold red]Bad request:[/] ({error.status_code}) {_format_api_error(error)}") console.print("[yellow]Hint:[/] Check your input values. Run with [cyan]--help[/] to see required options.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, ConflictError): console.print(f"[bold red]Conflict:[/] ({error.status_code}) {_format_api_error(error)}") console.print( "[yellow]Hint:[/] A resource with this name already exists. Try a different name or delete the existing one." ) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, RateLimitError): console.print(f"[bold red]Rate limit exceeded:[/] ({error.status_code}) {_format_api_error(error)}") console.print("[yellow]Hint:[/] Too many requests. Wait a moment and try again.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, InternalServerError): formatted = _format_api_error(error) console.print(f"[bold red]Server error:[/] ({error.status_code}) {formatted}") @@ -274,27 +276,27 @@ def handle_exception(error: Exception, ctx: click.Context | None = None) -> None console.print(f"[yellow]Hint:[/] {_format_not_found_hint(ctx, prog)}") else: console.print("[yellow]Hint:[/] This is a server-side issue. Try again later or contact support.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) + elif isinstance(error, APITimeoutError): + console.print(f"[bold red]Timeout error:[/] {_format_api_error(error)}") + _print_api_request_context(console, error) + console.print("[yellow]Hint:[/] The request timed out. The server may be busy - try again later.") + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIConnectionError): console.print(f"[bold red]Connection error:[/] {_format_api_error(error)}") _print_api_request_context(console, error) console.print( "[yellow]Hint:[/] Check your network connection and verify that [cyan]base-url[/] you configured is correct." ) - raise typer.Exit(code=1) - elif isinstance(error, APITimeoutError): - console.print(f"[bold red]Timeout error:[/] {_format_api_error(error)}") - _print_api_request_context(console, error) - console.print("[yellow]Hint:[/] The request timed out. The server may be busy - try again later.") - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIStatusError): console.print(f"[bold red]API error:[/] ({error.status_code}) {_format_api_error(error)}") _print_api_request_context(console, error) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, APIError): console.print(f"[bold red]API error:[/] {_format_api_error(error)}") _print_api_request_context(console, error) - raise typer.Exit(code=1) + raise typer.Exit(code=REMOTE_ERROR_EXIT_CODE) elif isinstance(error, ValueError) and "Missing workspace argument" in str(error): console.print("[bold red]Missing workspace:[/] No workspace configured for this command.") console.print( diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/core/formatters.py b/sdk/python/nemo-platform/src/nemo_platform/cli/core/formatters.py index 43f7b3dbe0..1cbb697e74 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/core/formatters.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/core/formatters.py @@ -10,9 +10,11 @@ import json import re import sys +from collections.abc import Iterator from dataclasses import dataclass -from typing import Any, Literal +from typing import Any +import click import yaml from rich.console import Console from rich.syntax import Syntax @@ -86,17 +88,29 @@ def _extract_items_from_response(data: Any) -> list[Any]: if isinstance(data, list): return data + return list(_iter_items_from_response(data)) + + +def _iter_items_from_response(data: Any) -> Iterator[Any]: + """Iterate list items from an API response without materializing callables.""" + if isinstance(data, list): + yield from data + return + for field in LIST_ITEM_FIELDS: if isinstance(data, dict): if field in data: - return data[field] + items = data[field] + else: + continue else: - value = getattr(data, field, None) - if value is None: + items = getattr(data, field, None) + if items is None: continue - return list(value()) if callable(value) else value + items = items() if callable(items) else items - return [] + yield from items + return def _to_dict_items(items: list[Any]) -> list[dict[str, Any]]: @@ -263,6 +277,19 @@ def format_json( return json_str +def iter_json_lines(data: Any, *, is_list: bool = False) -> Iterator[str]: + """Return newline-delimited JSON records for streaming-style output.""" + records = _iter_items_from_response(data) if is_list else iter((data,)) + for record in records: + yield json.dumps(model_to_dict(record), ensure_ascii=False, separators=(",", ":")) + + +def validate_stream_output_format(output_format: str | None, stream: bool) -> None: + """Validate output format compatibility before streaming data acquisition starts.""" + if stream and output_format not in {"json", "raw"}: + raise click.UsageError("--stream requires --output json or --output raw.") + + def format_yaml( data: Any, syntax_highlight: bool = True, @@ -571,12 +598,13 @@ def format_output( *, is_list: bool = False, output_format: str | None = None, - output_columns: Literal["all"] | list[Column] | None = None, + output_columns: str | list[Column] | None = None, indent: int = 2, no_truncate: bool | None = None, timestamp_format: str | None = None, wrap: bool = False, wrap_max_width: int | None = 90, + stream: bool = False, ) -> None: """ Format and print output to stdout. @@ -601,9 +629,20 @@ def format_output( uses the full terminal width. wrap_max_width: Per-column character cap for wrapped columns when `wrap=True` and `no_truncate=False`. Defaults to 90. + stream: Emit newline-delimited JSON records. List responses emit one + record per item; entity responses emit one record. """ from nemo_platform.cli.core.table_config import resolve_and_validate_columns, validate_output_columns + timestamp_format = timestamp_format or "iso" + + if stream: + validate_stream_output_format(output_format, stream) + for line in iter_json_lines(data, is_list=is_list): + click.echo(line) + sys.stdout.flush() + return + if not is_list and output_format in {"table", "markdown", "csv"}: output_format = "json" # Fallback to JSON for single items in these formats @@ -634,7 +673,7 @@ def format_output( if ( output_format == "table" and not truncate - and output_columns + and isinstance(output_columns, list) and len(output_columns) > MAX_TABLE_COLUMNS_WITHOUT_TRUNCATE ): print( @@ -653,6 +692,7 @@ def format_output( output = format_yaml(data, syntax_highlight=True, background=False) print(output) elif output_format == "table": + assert isinstance(output_columns, list) # Table format. When wrapping is on and --no-truncate is set, drop the # per-column cap so wrapping uses the full terminal width. effective_wrap_max_width = None if (wrap and not truncate) else wrap_max_width @@ -666,12 +706,14 @@ def format_output( ) print(output) elif output_format == "markdown": + assert isinstance(output_columns, list) # Markdown table format output = format_markdown_table( data, columns=output_columns, truncate=truncate, timestamp_format=timestamp_format ) print(output) elif output_format == "csv": + assert isinstance(output_columns, list) # CSV format output = format_csv(data, columns=output_columns, truncate=truncate, timestamp_format=timestamp_format) print(output, end="") # CSV already includes newlines diff --git a/sdk/python/nemo-platform/src/nemo_platform/cli/core/types.py b/sdk/python/nemo-platform/src/nemo_platform/cli/core/types.py index 04686d5ae9..85700af82a 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/cli/core/types.py +++ b/sdk/python/nemo-platform/src/nemo_platform/cli/core/types.py @@ -26,6 +26,7 @@ ListOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for the list of results.", @@ -37,6 +38,7 @@ EntityOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for an entity.", @@ -47,6 +49,7 @@ ConfigOutputFormat | None, typer.Option( "--output-format", + "--output", "-f", show_choices=True, help="Output format for config.", @@ -54,6 +57,15 @@ ), ] +StreamOutputOption = Annotated[ + bool, + typer.Option( + "--stream", + help="Emit newline-delimited JSON, one record per line. Requires JSON or raw output.", + rich_help_panel="Output Options", + ), +] + NoTruncateOption = Annotated[ bool | None, typer.Option( diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/skills/test_cli.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/skills/test_cli.py index f195fc630f..5a2140d14c 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/skills/test_cli.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/skills/test_cli.py @@ -4,6 +4,7 @@ """Tests for the skills CLI commands.""" from pathlib import Path +from unittest.mock import patch import pytest from nemo_platform.cli.app import app @@ -21,7 +22,11 @@ def _example_plugin_skills_root() -> Path: - return Path(__file__).resolve().parents[6] / "plugins" / "example-plugin" / "src" / "nemo_example_plugin" / "skills" + relative_path = Path("plugins/example-plugin/src/nemo_example_plugin/skills") + for parent in Path(__file__).resolve().parents: + if (parent / relative_path).is_dir(): + return parent / relative_path + raise FileNotFoundError(f"Could not find {relative_path} from {__file__}") def _platform_skill_names() -> set[str]: @@ -100,6 +105,14 @@ def test_list_duplicate_skill_error_is_clean(self, monkeypatch): assert_exit_code(result, 1) assert "Error: duplicate skill" in result.output + def test_stream_requires_json_output_before_loading_skills(self): + with patch("nemo_platform.cli.commands.skills.cli.load_skills") as mock_load_skills: + result = runner.invoke(app, "skills list --output table --stream") + + assert_exit_code(result, 2) + assert "--stream requires --output json or --output raw" in result.stderr + mock_load_skills.assert_not_called() + def test_list_source_column_shows_distribution_names(self, example_plugin_skills): """JSON output is stable and easy to assert against the new ``Source`` semantics. diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/test_plugins.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/test_plugins.py index 4a30a7fda4..dae7766f56 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/test_plugins.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/test_plugins.py @@ -43,6 +43,8 @@ def test_plugins_list_help(self): assert_exit_code(result, 0) assert "List installed plugins." in result.stdout assert "--output-format" in result.stdout + assert "--output" in result.stdout + assert "--stream" in result.stdout class TestPluginsList: @@ -88,6 +90,40 @@ def test_list_json_output(self): assert '"version": "0.1.0"' in result.stdout assert '"description": "Example plugin"' in result.stdout + def test_list_accepts_output_alias(self): + manifests = { + "example": PluginManifest(name="example", version="0.1.0", description="Example plugin"), + } + + with patch("nemo_platform_plugin.discovery.discover_manifests", return_value=manifests): + result = _invoke("plugins", "list", "--output", "json") + + assert_exit_code(result, 0) + assert '"name": "example"' in result.stdout + + def test_list_streams_json_lines(self): + manifests = { + "beta": PluginManifest(name="beta", version="1.5.0", description="Beta plugin"), + "alpha": PluginManifest(name="alpha", version="1.0.0", description="Alpha plugin"), + } + + with patch("nemo_platform_plugin.discovery.discover_manifests", return_value=manifests): + result = _invoke("plugins", "list", "--output", "json", "--stream") + + assert_exit_code(result, 0) + assert result.stdout.splitlines() == [ + '{"name":"alpha","version":"1.0.0","description":"Alpha plugin"}', + '{"name":"beta","version":"1.5.0","description":"Beta plugin"}', + ] + + def test_stream_requires_json_output(self): + with patch("nemo_platform_plugin.discovery.discover_manifests") as mock_discover: + result = _invoke("plugins", "list", "--output", "table", "--stream") + + assert_exit_code(result, 2) + assert "--stream requires --output json or --output raw" in result.stderr + mock_discover.assert_not_called() + def test_list_empty_result(self): with patch("nemo_platform_plugin.discovery.discover_manifests", return_value={}): result = _invoke("plugins", "list", "-f", "json") diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/use_cases/test_chat.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/use_cases/test_chat.py index 568c0b6ad5..d49fcb2d0f 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/use_cases/test_chat.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/commands/use_cases/test_chat.py @@ -29,6 +29,8 @@ from nemo_platform.cli.commands.use_cases.chat import _parse_model_and_workspace from typer.testing import CliRunner +REMOTE_ERROR_EXIT_CODE = 3 + # ============================================================================= # Unit Tests for _parse_model_and_workspace # ============================================================================= @@ -80,7 +82,7 @@ def test_parse_model_and_workspace_no_workspace_error() -> None: # CLI Syntax Validation Tests # # These tests verify the CLI accepts valid syntax. They run against a fake -# server URL so commands fail with connection errors (exit code 1), not +# server URL so commands fail with connection errors (exit code 3), not # syntax errors (exit code 2). # ============================================================================= @@ -139,20 +141,19 @@ def test_chat_missing_model_fails_with_usage_error(runner: CliRunner) -> None: def test_chat_model_only(runner: CliRunner) -> None: """Chat with just model argument parses correctly.""" result = runner.invoke(app, ["chat", "my-model", "hello"]) - # Exit code 1 = runtime error (connection), not 2 = syntax error - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_model_with_inline_workspace(runner: CliRunner) -> None: """Chat with workspace/model syntax parses correctly.""" result = runner.invoke(app, ["chat", "my-workspace/my-model", "hello"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_workspace_flag(runner: CliRunner) -> None: """Chat with --workspace flag parses correctly.""" result = runner.invoke(app, ["chat", "my-model", "hello", "--workspace", "test-ws"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_provider_flag(runner: CliRunner) -> None: @@ -160,7 +161,7 @@ def test_chat_with_provider_flag(runner: CliRunner) -> None: result = runner.invoke( app, ["chat", "nvidia/model-id", "hello", "--provider", "nvidia-build", "--workspace", "test-ws"] ) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_provider_rejects_workspace_prefix(runner: CliRunner) -> None: @@ -177,19 +178,19 @@ def test_chat_provider_rejects_workspace_prefix(runner: CliRunner) -> None: def test_chat_with_temperature(runner: CliRunner) -> None: """Chat with --temperature parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--temperature", "0.7"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_max_tokens(runner: CliRunner) -> None: """Chat with --max-tokens parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--max-tokens", "512"]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_with_system_message(runner: CliRunner) -> None: """Chat with --system-message parses correctly.""" result = runner.invoke(app, ["chat", "ws/model", "hello", "--system-message", "You are helpful."]) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_all_options(runner: CliRunner) -> None: @@ -212,7 +213,7 @@ def test_chat_all_options(runner: CliRunner) -> None: "Be concise.", ], ) - assert result.exit_code == 1 + assert result.exit_code == REMOTE_ERROR_EXIT_CODE def test_chat_temperature_rejects_non_numeric(runner: CliRunner) -> None: diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_errors.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_errors.py index e108c45652..02af2c4741 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_errors.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_errors.py @@ -30,6 +30,8 @@ ) from typer.testing import CliRunner +DOCUMENTED_REMOTE_ERROR_EXIT_CODE = 3 + @pytest.mark.parametrize( "body,message,expected", @@ -78,7 +80,7 @@ def test_handle_api_status_errors(capsys, error_class, status_code, expected_pre with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert expected_prefix in captured.err assert f"({status_code})" in captured.err @@ -92,7 +94,7 @@ def test_handle_api_connection_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Connection error:" in captured.err assert "base-url" in captured.err @@ -105,9 +107,9 @@ def test_handle_api_timeout_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() - assert "Connection error:" in captured.err + assert "Timeout error:" in captured.err assert "timed out" in captured.err @@ -119,7 +121,7 @@ def test_handle_api_status_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err assert "(418)" in captured.err @@ -133,7 +135,7 @@ def test_handle_api_status_error_prints_request_context(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err assert "Request: POST http://test/apis/models/v2/workspaces/default/models" in captured.err @@ -147,7 +149,7 @@ def test_handle_generic_api_error(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "API error:" in captured.err @@ -281,7 +283,7 @@ def failing_function(): with pytest.raises(typer.Exit) as exc_info: failing_function() - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err @@ -295,7 +297,7 @@ def test_not_found_prints_server_message(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err assert "Workspace 'default' not found" in captured.err @@ -309,7 +311,7 @@ def test_not_found_prints_request_context_and_404_hint(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Request: GET http://test/apis/agents/v2/workspaces/default/agents" in captured.err assert "Target: agents API route /apis/agents/v2/workspaces/default/agents" in captured.err @@ -327,7 +329,7 @@ def test_not_found_hint_fallback_when_no_ctx(capsys): with pytest.raises(typer.Exit) as exc_info: handle_exception(error) - assert exc_info.value.exit_code == 1 + assert exc_info.value.exit_code == DOCUMENTED_REMOTE_ERROR_EXIT_CODE captured = capsys.readouterr() assert "Not found:" in captured.err assert "Check the resource name/ID" in captured.err diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_formatters.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_formatters.py index c9fdbc5b6a..23ea34ef3c 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_formatters.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/core/test_formatters.py @@ -7,6 +7,7 @@ import re from unittest.mock import Mock, patch +import click import pytest import yaml from nemo_platform.cli.core.formatters import ( @@ -19,7 +20,9 @@ format_stream_event, format_table, format_yaml, + iter_json_lines, model_to_dict, + validate_stream_output_format, ) @@ -100,6 +103,67 @@ def test_format_json_nested_data(): assert parsed == data +def test_iter_json_lines_extracts_list_items(): + """JSON Lines output should emit one compact JSON object per list item.""" + mock_response = Mock() + mock_response.data = [ + {"id": "1", "name": "Alice"}, + {"id": "2", "name": "Bob"}, + ] + + assert list(iter_json_lines(mock_response, is_list=True)) == [ + '{"id":"1","name":"Alice"}', + '{"id":"2","name":"Bob"}', + ] + + +def test_iter_json_lines_keeps_callable_items_lazy(): + """Callable response item sources should stream without list materialization.""" + + class LazyResponse: + def __init__(self) -> None: + self.yielded = 0 + + def items(self): + for item in [{"id": "1"}, {"id": "2"}]: + self.yielded += 1 + yield item + + response = LazyResponse() + lines = iter_json_lines(response, is_list=True) + + assert response.yielded == 0 + assert next(lines) == '{"id":"1"}' + assert response.yielded == 1 + assert next(lines) == '{"id":"2"}' + assert response.yielded == 2 + with pytest.raises(StopIteration): + next(lines) + + +def test_format_output_streams_json_lines(capsys): + """format_output should print list items as newline-delimited JSON when streaming.""" + mock_response = Mock() + mock_response.data = [{"id": "1"}, {"id": "2"}] + + format_output(mock_response, is_list=True, output_format="json", stream=True) + + captured = capsys.readouterr() + assert captured.out.splitlines() == ['{"id":"1"}', '{"id":"2"}'] + + +def test_format_output_stream_requires_json_compatible_format(): + """--stream is only valid for JSON-compatible formats.""" + with pytest.raises(click.UsageError, match="--stream requires --output json or --output raw"): + format_output([{"id": "1"}], is_list=True, output_format="table", stream=True) + + +def test_validate_stream_output_format_rejects_table_output(): + """Command handlers can fail before loading API or discovery data.""" + with pytest.raises(click.UsageError, match="--stream requires --output json or --output raw"): + validate_stream_output_format("table", True) + + @patch("nemo_platform.cli.core.formatters.is_tty") def test_format_yaml_no_highlighting(mock_is_tty): """Test YAML formatting without syntax highlighting.""" diff --git a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/test_app.py b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/test_app.py index d9cab1c087..dda2f46b78 100644 --- a/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/test_app.py +++ b/sdk/python/nemo-platform/tests/vendored/nemo_platform_ext/cli/test_app.py @@ -52,13 +52,43 @@ def test_help_includes_getting_started(): assert "Getting started:" in result.stdout assert "nemo docs --list" in result.stdout assert "nemo services run --help" in result.stdout + assert "Exit codes:" in result.stdout + assert "3: Remote/API error" in result.stdout # Help panel truncation depends on terminal width; match a stable prefix. assert "Set up NeMo Platform" in result.stdout + assert "--output-format, --output, -f" in result.stdout assert "--help, -h" in result.stdout assert "nemo auth login --base-url" not in result.stdout assert "nemo quickstart configure" not in result.stdout +def test_generated_list_help_includes_stream_option(): + runner = CliRunner() + qs_config = QuickstartConfig(auth_enabled=False) + + with patch("nemo_platform.quickstart.QuickstartConfig.load", return_value=qs_config): + result = runner.invoke(app, ["models", "list", "--help"]) + + assert result.exit_code == 0 + assert "--stream" in result.stdout + assert "--output-format, --output, -f" in result.stdout + + +def test_generated_list_validates_stream_output_before_client_setup(): + runner = CliRunner() + qs_config = QuickstartConfig(auth_enabled=False) + + with ( + patch("nemo_platform.quickstart.QuickstartConfig.load", return_value=qs_config), + patch("nemo_platform.cli.core.context.CLIContext.get_client") as mock_get_client, + ): + result = runner.invoke(app, ["workspaces", "list", "--output", "table", "--stream"]) + + assert result.exit_code == 2 + assert "--stream requires --output json or --output raw" in result.stderr + mock_get_client.assert_not_called() + + @pytest.mark.parametrize( ("argv", "expected_text"), [ diff --git a/tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/templates/list_command.py.j2 b/tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/templates/list_command.py.j2 index 9c6ae8162e..ee1fe8eefa 100644 --- a/tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/templates/list_command.py.j2 +++ b/tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/templates/list_command.py.j2 @@ -6,10 +6,10 @@ from nemo_platform_ext.cli.core.api import build_kwargs, merge_filter_dict from nemo_platform_ext.cli.core.code_generator import handle_code_generation from nemo_platform_ext.cli.core.context import CLIContext from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output +from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output, validate_stream_output_format from nemo_platform_ext.cli.core.help_formatter import collect_warnings from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages -from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption +from nemo_platform_ext.cli.core.types import ListOutputFormatOption, NoTruncateOption, OutputColumnsOption, StreamOutputOption @app.command("{{ cli_command_name }}") @@ -22,11 +22,13 @@ def {{ function_name }}( output_format: ListOutputFormatOption = None, no_truncate: NoTruncateOption = None, columns: OutputColumnsOption = None, + stream: StreamOutputOption = False, {% if pagination_type != "NOT_PAGINATED" %}all_pages: Annotated[bool, typer.Option("--all-pages", help="Fetch all pages")] = False,{% endif %} ) -> None: """{{ help_text }}""" state: CLIContext = ctx.obj output_format = state.get_output_format(output_format) + validate_stream_output_format(output_format, stream) check_output_columns_with_format(columns, output_format) @@ -63,6 +65,7 @@ def {{ function_name }}( output_columns=columns, no_truncate=state.get_no_truncate(no_truncate), timestamp_format=state.get_timestamp_format(), + stream=stream, ) {% if pagination_type != "NOT_PAGINATED" %} if not all_pages: