cloudos-cli 2.93.0__tar.gz → 2.94.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/PKG-INFO +31 -1
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/README.md +30 -0
- cloudos_cli-2.94.0/cloudos_cli/_version.py +1 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/interactive_session/cli.py +13 -6
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/interactive_session/interactive_session.py +186 -105
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/queue/__init__.py +1 -1
- cloudos_cli-2.94.0/cloudos_cli/queue/cli.py +290 -0
- cloudos_cli-2.94.0/cloudos_cli/queue/queue.py +426 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/errors.py +19 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/PKG-INFO +31 -1
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/test_list_sessions.py +149 -0
- cloudos_cli-2.93.0/cloudos_cli/_version.py +0 -1
- cloudos_cli-2.93.0/cloudos_cli/queue/cli.py +0 -95
- cloudos_cli-2.93.0/cloudos_cli/queue/queue.py +0 -166
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/LICENSE +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/__main__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/bash/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/bash/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/clos.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/configure/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/constants.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/cost/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/cost/cost.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/cromwell/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/cromwell/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/datasets/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/datasets/datasets.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/import_wf/import_wf.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/interactive_session/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/interactive_session/link.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/jobs/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/logging/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/logging/logger.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/procurement/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/procurement/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/procurement/images.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/projects/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/projects/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/related_analyses/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/related_analyses/related_analyses.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/cli_helpers.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/details.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/last_wf.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/nextflow_version.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/workflows/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/workflows/cli.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/SOURCES.txt +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/setup.cfg +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/setup.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/functions_for_pytest.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_cli_project_create.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_cost/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_cost/test_job_cost.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_details.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_error_messages.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/test_create_session.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/test_link_error_handling.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/test_link_session.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_interactive_session/test_normalize_path.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_logging/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_logging/test_logger.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_nextflow_version.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_related_analyses/__init__.py +0 -0
- {cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/tests/test_related_analyses/test_related_analyses.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.94.0
|
|
4
4
|
Summary: Python package for interacting with the Lifebit Platform
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -64,6 +64,7 @@ Python package for interacting with Lifebit Platform
|
|
|
64
64
|
- [Create Projects](#create-projects)
|
|
65
65
|
- [Queue](#queue)
|
|
66
66
|
- [List Queues](#list-queues)
|
|
67
|
+
- [Create Queue](#create-queue)
|
|
67
68
|
- [Workflow](#workflow)
|
|
68
69
|
- [List All Available Workflows](#list-all-available-workflows)
|
|
69
70
|
- [Import a Nextflow Workflow](#import-a-nextflow-workflow)
|
|
@@ -476,6 +477,35 @@ cloudos queue list --profile my_profile --output-format csv
|
|
|
476
477
|
|
|
477
478
|
> NOTE: The queue name that is visible in Lifebit Platform and must be used with the `--job-queue` parameter is the one in the `label` field.
|
|
478
479
|
|
|
480
|
+
#### Create Queue
|
|
481
|
+
|
|
482
|
+
You can create a new AWS batch job queue in your Lifebit Platform workspace using the `queue create` command. By default the queue is built from a preset template, selected with the `--preset` option. Available presets are:
|
|
483
|
+
|
|
484
|
+
- **standard-stable** (default): On-demand stable instances
|
|
485
|
+
- **standard-cost-saving**: Spot instances (up to ~80% cheaper, at risk of premature termination)
|
|
486
|
+
- **read-write-optimised**: On-demand instances with increased disk I/O performance
|
|
487
|
+
- **standard-gpu**: On-demand standard instances plus GPU instances
|
|
488
|
+
|
|
489
|
+
To create a queue from a preset:
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
cloudos queue create --profile my_profile --label "my-new-queue" --description "Queue for RNA-seq jobs" --preset standard-stable
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Before creating the queue, a summary is shown and confirmation is requested. To skip the confirmation prompt, add the `-y`/`--yes` flag. You can also set the new queue as the workspace default with `--set-default`.
|
|
496
|
+
|
|
497
|
+
The expected output is something similar to:
|
|
498
|
+
|
|
499
|
+
```console
|
|
500
|
+
Executing queue create...
|
|
501
|
+
Queue "my-new-queue" created successfully.
|
|
502
|
+
Queue ID : 64f1a23b8e4c9d001234abcd
|
|
503
|
+
View at : https://cloudos.lifebit.ai/app/job-queues/64f1a23b8e4c9d001234abcd
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
> [!NOTE]
|
|
507
|
+
> **Azure Platform**: Batch job queues are an AWS-only feature and are not available in Azure or HPC workspaces.
|
|
508
|
+
|
|
479
509
|
**Job queues for platform workflows**
|
|
480
510
|
|
|
481
511
|
Platform workflows (those provided by Lifebit Platform in your workspace as modules) run on separate and specific AWS batch queues (system queues). Therefore, Lifebit Platform will automatically assign the valid queue and you should not specify any queue using the `--job-queue` parameter. Any attempt to use this parameter will be ignored. Examples of such platform workflows are "System Tools" and "Data Factory" workflows.
|
|
@@ -29,6 +29,7 @@ Python package for interacting with Lifebit Platform
|
|
|
29
29
|
- [Create Projects](#create-projects)
|
|
30
30
|
- [Queue](#queue)
|
|
31
31
|
- [List Queues](#list-queues)
|
|
32
|
+
- [Create Queue](#create-queue)
|
|
32
33
|
- [Workflow](#workflow)
|
|
33
34
|
- [List All Available Workflows](#list-all-available-workflows)
|
|
34
35
|
- [Import a Nextflow Workflow](#import-a-nextflow-workflow)
|
|
@@ -441,6 +442,35 @@ cloudos queue list --profile my_profile --output-format csv
|
|
|
441
442
|
|
|
442
443
|
> NOTE: The queue name that is visible in Lifebit Platform and must be used with the `--job-queue` parameter is the one in the `label` field.
|
|
443
444
|
|
|
445
|
+
#### Create Queue
|
|
446
|
+
|
|
447
|
+
You can create a new AWS batch job queue in your Lifebit Platform workspace using the `queue create` command. By default the queue is built from a preset template, selected with the `--preset` option. Available presets are:
|
|
448
|
+
|
|
449
|
+
- **standard-stable** (default): On-demand stable instances
|
|
450
|
+
- **standard-cost-saving**: Spot instances (up to ~80% cheaper, at risk of premature termination)
|
|
451
|
+
- **read-write-optimised**: On-demand instances with increased disk I/O performance
|
|
452
|
+
- **standard-gpu**: On-demand standard instances plus GPU instances
|
|
453
|
+
|
|
454
|
+
To create a queue from a preset:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
cloudos queue create --profile my_profile --label "my-new-queue" --description "Queue for RNA-seq jobs" --preset standard-stable
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Before creating the queue, a summary is shown and confirmation is requested. To skip the confirmation prompt, add the `-y`/`--yes` flag. You can also set the new queue as the workspace default with `--set-default`.
|
|
461
|
+
|
|
462
|
+
The expected output is something similar to:
|
|
463
|
+
|
|
464
|
+
```console
|
|
465
|
+
Executing queue create...
|
|
466
|
+
Queue "my-new-queue" created successfully.
|
|
467
|
+
Queue ID : 64f1a23b8e4c9d001234abcd
|
|
468
|
+
View at : https://cloudos.lifebit.ai/app/job-queues/64f1a23b8e4c9d001234abcd
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
> [!NOTE]
|
|
472
|
+
> **Azure Platform**: Batch job queues are an AWS-only feature and are not available in Azure or HPC workspaces.
|
|
473
|
+
|
|
444
474
|
**Job queues for platform workflows**
|
|
445
475
|
|
|
446
476
|
Platform workflows (those provided by Lifebit Platform in your workspace as modules) run on separate and specific AWS batch queues (system queues). Therefore, Lifebit Platform will automatically assign the valid queue and you should not specify any queue using the `--job-queue` parameter. Any attempt to use this parameter will be ignored. Examples of such platform workflows are "System Tools" and "Data Factory" workflows.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.94.0'
|
|
@@ -29,7 +29,9 @@ from cloudos_cli.interactive_session.interactive_session import (
|
|
|
29
29
|
format_stop_success_output,
|
|
30
30
|
poll_session_termination,
|
|
31
31
|
build_resume_payload,
|
|
32
|
-
fetch_interactive_session_page
|
|
32
|
+
fetch_interactive_session_page,
|
|
33
|
+
APP_SESSION_TYPES,
|
|
34
|
+
build_app_filtered_page_fetcher
|
|
33
35
|
)
|
|
34
36
|
from cloudos_cli.configure.configure import with_profile_config, CLOUDOS_URL
|
|
35
37
|
from cloudos_cli.utils.cli_helpers import pass_debug_to_subcommands
|
|
@@ -223,11 +225,16 @@ def list_sessions(ctx,
|
|
|
223
225
|
sessions = result.get('sessions', [])
|
|
224
226
|
pagination_metadata = result.get('pagination_metadata', None)
|
|
225
227
|
|
|
226
|
-
# Create callback function for fetching additional pages
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
# Create callback function for fetching additional pages.
|
|
229
|
+
# App sessions must be filtered here too so that navigating to
|
|
230
|
+
# next/prev pages via interactive pagination never re-introduces them.
|
|
231
|
+
fetch_page = build_app_filtered_page_fetcher(
|
|
232
|
+
cl, workspace_id, limit, filter_status, filter_only_mine, archived, verify_ssl
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Filter out app sessions (awsCustomSession / azureCustomSession) — not supported via API key
|
|
236
|
+
# Client-side filter; the API has no type-exclusion parameter
|
|
237
|
+
sessions = [s for s in sessions if s.get('interactiveSessionType') not in APP_SESSION_TYPES]
|
|
231
238
|
|
|
232
239
|
# Handle empty results
|
|
233
240
|
if len(sessions) == 0:
|
{cloudos_cli-2.93.0 → cloudos_cli-2.94.0}/cloudos_cli/interactive_session/interactive_session.py
RENAMED
|
@@ -57,6 +57,9 @@ def validate_instance_type(instance_type, execution_platform='aws'):
|
|
|
57
57
|
return True, None
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
APP_SESSION_TYPES = frozenset({'awsCustomSession', 'azureCustomSession'})
|
|
61
|
+
|
|
62
|
+
|
|
60
63
|
def _map_session_type_to_friendly_name(session_type):
|
|
61
64
|
"""Map internal session type names to user-friendly display names.
|
|
62
65
|
|
|
@@ -83,7 +86,9 @@ def _map_session_type_to_friendly_name(session_type):
|
|
|
83
86
|
'azureSpark': 'Spark',
|
|
84
87
|
'awsRStudio': 'RStudio', # Handle both capitalizations
|
|
85
88
|
'azureRStudio': 'RStudio',
|
|
86
|
-
'awsWindowsSession': 'Windows'
|
|
89
|
+
'awsWindowsSession': 'Windows',
|
|
90
|
+
'awsCustomSession': 'App',
|
|
91
|
+
'azureCustomSession': 'App'
|
|
87
92
|
}
|
|
88
93
|
return type_mapping.get(session_type, session_type)
|
|
89
94
|
|
|
@@ -251,50 +256,95 @@ def create_interactive_session_list_table(sessions, pagination_metadata=None, se
|
|
|
251
256
|
if len(sessions) == 0:
|
|
252
257
|
console.print('[yellow]No interactive sessions found.[/yellow]')
|
|
253
258
|
return
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
#
|
|
259
|
+
# ------------------------------------------------------------------
|
|
260
|
+
# Row-builder helper (keeps column-mapping in one place)
|
|
261
|
+
# ------------------------------------------------------------------
|
|
262
|
+
def _build_rows(sess_list):
|
|
263
|
+
result = []
|
|
264
|
+
for sess in sess_list:
|
|
265
|
+
row_data = []
|
|
266
|
+
for col_name in columns_to_show:
|
|
267
|
+
if col_name not in all_columns:
|
|
268
|
+
continue
|
|
269
|
+
col_config = all_columns[col_name]
|
|
270
|
+
value = _get_nested_value(sess, col_config['accessor'])
|
|
271
|
+
row_data.append(_format_session_field(col_name, value))
|
|
272
|
+
result.append(row_data)
|
|
273
|
+
return result
|
|
274
|
+
|
|
275
|
+
# ------------------------------------------------------------------
|
|
276
|
+
# Pagination state
|
|
277
|
+
# ------------------------------------------------------------------
|
|
271
278
|
if pagination_metadata:
|
|
272
|
-
# Server-side pagination
|
|
279
|
+
# Server-side pagination with fill-buffer.
|
|
280
|
+
# The server page may contain app-session types that are filtered
|
|
281
|
+
# client-side, leaving fewer than page_size rows. We keep a
|
|
282
|
+
# carry_forward buffer and silently fetch ahead so that every
|
|
283
|
+
# virtual page is exactly page_size rows (or fewer only on the
|
|
284
|
+
# very last page).
|
|
273
285
|
current_api_page = pagination_metadata.get('page', 1)
|
|
274
|
-
total_sessions
|
|
275
|
-
total_pages
|
|
286
|
+
total_sessions = pagination_metadata.get('count', len(sessions))
|
|
287
|
+
total_pages = pagination_metadata.get('totalPages', 1)
|
|
288
|
+
|
|
289
|
+
carry_forward = list(sessions) # fetched but not yet shown
|
|
290
|
+
last_fetched_server_page = current_api_page
|
|
291
|
+
virtual_page_history = [] # for "prev" navigation
|
|
292
|
+
|
|
293
|
+
def _top_up():
|
|
294
|
+
"""Silently fetch ahead until carry_forward >= page_size or exhausted."""
|
|
295
|
+
nonlocal last_fetched_server_page, total_pages
|
|
296
|
+
if not fetch_page_callback:
|
|
297
|
+
return
|
|
298
|
+
while (len(carry_forward) < page_size
|
|
299
|
+
and last_fetched_server_page < total_pages):
|
|
300
|
+
try:
|
|
301
|
+
extra = fetch_page_callback(last_fetched_server_page + 1)
|
|
302
|
+
carry_forward.extend(extra.get('sessions', []))
|
|
303
|
+
meta = extra.get('pagination_metadata', {})
|
|
304
|
+
total_pages = meta.get('totalPages', total_pages)
|
|
305
|
+
last_fetched_server_page += 1
|
|
306
|
+
except Exception:
|
|
307
|
+
break
|
|
308
|
+
|
|
309
|
+
# Fill the first virtual page
|
|
310
|
+
_top_up()
|
|
311
|
+
current_page_sessions = carry_forward[:page_size]
|
|
312
|
+
del carry_forward[:page_size]
|
|
313
|
+
|
|
276
314
|
else:
|
|
277
|
-
# Client-side pagination (
|
|
278
|
-
current_api_page
|
|
279
|
-
total_sessions
|
|
280
|
-
total_pages
|
|
281
|
-
|
|
315
|
+
# Client-side pagination (all sessions already in memory)
|
|
316
|
+
current_api_page = 0
|
|
317
|
+
total_sessions = len(sessions)
|
|
318
|
+
total_pages = (len(sessions) + page_size - 1) // page_size if sessions else 1
|
|
319
|
+
current_page_sessions = None # unused in client-side path
|
|
320
|
+
carry_forward = []
|
|
321
|
+
last_fetched_server_page = 0
|
|
322
|
+
virtual_page_history = []
|
|
323
|
+
|
|
324
|
+
virtual_page_num = 1
|
|
325
|
+
show_error = None # Track error messages to display
|
|
326
|
+
|
|
282
327
|
while True:
|
|
283
|
-
#
|
|
284
|
-
|
|
328
|
+
# ---- Build display rows for the current virtual page ----------------
|
|
329
|
+
if fetch_page_callback and pagination_metadata:
|
|
330
|
+
page_rows = _build_rows(current_page_sessions)
|
|
331
|
+
else:
|
|
332
|
+
all_rows = _build_rows(sessions)
|
|
333
|
+
start = current_api_page * page_size
|
|
334
|
+
page_rows = all_rows[start:start + page_size]
|
|
335
|
+
|
|
336
|
+
# ---- Determine navigation availability ------------------------------
|
|
285
337
|
if fetch_page_callback and pagination_metadata:
|
|
286
|
-
|
|
287
|
-
|
|
338
|
+
has_next = bool(carry_forward) or last_fetched_server_page < total_pages
|
|
339
|
+
has_prev = bool(virtual_page_history)
|
|
288
340
|
else:
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
#
|
|
341
|
+
has_next = (current_api_page + 1) * page_size < len(sessions)
|
|
342
|
+
has_prev = current_api_page > 0
|
|
343
|
+
show_nav = has_next or has_prev
|
|
344
|
+
|
|
345
|
+
# ---- Render table --------------------------------------------------
|
|
294
346
|
console.clear()
|
|
295
|
-
# Create table
|
|
296
347
|
table = Table(title='Interactive Sessions')
|
|
297
|
-
# Add columns to table
|
|
298
348
|
for col_name in columns_to_show:
|
|
299
349
|
if col_name not in all_columns:
|
|
300
350
|
continue
|
|
@@ -304,100 +354,86 @@ def create_interactive_session_list_table(sessions, pagination_metadata=None, se
|
|
|
304
354
|
style=col_config.get('style', 'white'),
|
|
305
355
|
no_wrap=col_config.get('no_wrap', False)
|
|
306
356
|
)
|
|
307
|
-
# Add rows to table
|
|
308
357
|
for row in page_rows:
|
|
309
358
|
table.add_row(*row)
|
|
310
|
-
# Print table
|
|
311
359
|
console.print(table)
|
|
312
|
-
# Display pagination info
|
|
313
|
-
console.print(f"\n[cyan]Total sessions:[/cyan] {total_sessions}")
|
|
314
|
-
if total_pages > 1:
|
|
315
|
-
console.print(f"[cyan]Page:[/cyan] {current_api_page} of {total_pages}")
|
|
316
|
-
console.print(f"[cyan]Sessions on this page:[/cyan] {len(page_rows)}")
|
|
317
360
|
|
|
318
|
-
#
|
|
361
|
+
# ---- Pagination info ------------------------------------------------
|
|
362
|
+
# Sessions on this page is always the exact post-filter count.
|
|
363
|
+
# The virtual page number counts only pages with at least one session.
|
|
364
|
+
if show_nav:
|
|
365
|
+
page_display = virtual_page_num if (fetch_page_callback and pagination_metadata) else current_api_page + 1
|
|
366
|
+
console.print(f"\n[cyan]Page:[/cyan] {page_display}")
|
|
367
|
+
elif not pagination_metadata:
|
|
368
|
+
# Client-side, single page: total is exact
|
|
369
|
+
console.print(f"\n[cyan]Total sessions:[/cyan] {total_sessions}")
|
|
370
|
+
console.print(f"[cyan]Sessions on this page:[/cyan] {len(page_rows)}")
|
|
371
|
+
if fetch_page_callback and pagination_metadata:
|
|
372
|
+
console.print("[yellow]Note: total page count is not available from the CLI for interactive sessions. This feature will be ready soon.[/yellow]")
|
|
373
|
+
|
|
374
|
+
# ---- Error message --------------------------------------------------
|
|
319
375
|
if show_error:
|
|
320
376
|
console.print(show_error)
|
|
321
|
-
show_error = None
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
show_error = None
|
|
378
|
+
|
|
379
|
+
# ---- Navigation controls -------------------------------------------
|
|
380
|
+
if show_nav:
|
|
325
381
|
if not sys.stdin.isatty():
|
|
326
|
-
|
|
382
|
+
page_display = virtual_page_num if (fetch_page_callback and pagination_metadata) else current_api_page + 1
|
|
383
|
+
console.print(f"\n[yellow]Note: Pagination not available in non-interactive mode. Showing page {page_display}.[/yellow]")
|
|
327
384
|
console.print("[yellow]Run in an interactive terminal to navigate through all pages.[/yellow]")
|
|
328
385
|
break
|
|
329
386
|
console.print(f"\n[bold cyan]n[/] = next, [bold cyan]p[/] = prev, [bold cyan]q[/] = quit")
|
|
330
|
-
# Get user input for navigation
|
|
331
387
|
try:
|
|
332
388
|
choice = input(">>> ").strip().lower()
|
|
333
389
|
except (EOFError, KeyboardInterrupt):
|
|
334
|
-
# Handle non-interactive environments or user interrupt
|
|
335
390
|
console.print("\n[yellow]Pagination interrupted.[/yellow]")
|
|
336
391
|
break
|
|
392
|
+
|
|
337
393
|
if choice in ("q", "quit"):
|
|
338
394
|
break
|
|
395
|
+
|
|
339
396
|
elif choice in ("n", "next"):
|
|
340
|
-
if
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for session in sessions:
|
|
352
|
-
row_data = []
|
|
353
|
-
for col_name in columns_to_show:
|
|
354
|
-
if col_name not in all_columns:
|
|
355
|
-
continue
|
|
356
|
-
col_config = all_columns[col_name]
|
|
357
|
-
accessor = col_config['accessor']
|
|
358
|
-
value = _get_nested_value(session, accessor)
|
|
359
|
-
formatted_value = _format_session_field(col_name, value)
|
|
360
|
-
row_data.append(formatted_value)
|
|
361
|
-
rows.append(row_data)
|
|
362
|
-
except Exception as e:
|
|
363
|
-
show_error = f"[red]Error fetching next page: {str(e)}[/red]"
|
|
397
|
+
if fetch_page_callback and pagination_metadata:
|
|
398
|
+
if carry_forward or last_fetched_server_page < total_pages:
|
|
399
|
+
virtual_page_history.append(current_page_sessions[:])
|
|
400
|
+
_top_up()
|
|
401
|
+
if carry_forward:
|
|
402
|
+
current_page_sessions = carry_forward[:page_size]
|
|
403
|
+
del carry_forward[:page_size]
|
|
404
|
+
virtual_page_num += 1
|
|
405
|
+
else:
|
|
406
|
+
virtual_page_history.pop()
|
|
407
|
+
show_error = "[yellow]No more sessions found.[/yellow]"
|
|
364
408
|
else:
|
|
365
|
-
|
|
409
|
+
show_error = "[red]Already on the last page.[/red]"
|
|
366
410
|
else:
|
|
367
|
-
|
|
411
|
+
if has_next:
|
|
412
|
+
current_api_page += 1
|
|
413
|
+
else:
|
|
414
|
+
show_error = "[red]Already on the last page.[/red]"
|
|
415
|
+
|
|
368
416
|
elif choice in ("p", "prev"):
|
|
369
|
-
if
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
current_api_page = pagination_metadata.get('page', current_api_page - 1)
|
|
377
|
-
total_pages = pagination_metadata.get('totalPages', total_pages)
|
|
378
|
-
# Rebuild rows for the new page
|
|
379
|
-
rows = []
|
|
380
|
-
for session in sessions:
|
|
381
|
-
row_data = []
|
|
382
|
-
for col_name in columns_to_show:
|
|
383
|
-
if col_name not in all_columns:
|
|
384
|
-
continue
|
|
385
|
-
col_config = all_columns[col_name]
|
|
386
|
-
accessor = col_config['accessor']
|
|
387
|
-
value = _get_nested_value(session, accessor)
|
|
388
|
-
formatted_value = _format_session_field(col_name, value)
|
|
389
|
-
row_data.append(formatted_value)
|
|
390
|
-
rows.append(row_data)
|
|
391
|
-
except Exception as e:
|
|
392
|
-
show_error = f"[red]Error fetching previous page: {str(e)}[/red]"
|
|
417
|
+
if fetch_page_callback and pagination_metadata:
|
|
418
|
+
if virtual_page_history:
|
|
419
|
+
# Return current sessions to the front of carry_forward,
|
|
420
|
+
# then restore the previous virtual page from history.
|
|
421
|
+
carry_forward[:0] = current_page_sessions
|
|
422
|
+
current_page_sessions = virtual_page_history.pop()
|
|
423
|
+
virtual_page_num -= 1
|
|
393
424
|
else:
|
|
394
|
-
|
|
425
|
+
show_error = "[red]Already on the first page.[/red]"
|
|
395
426
|
else:
|
|
396
|
-
|
|
427
|
+
if has_prev:
|
|
428
|
+
current_api_page -= 1
|
|
429
|
+
else:
|
|
430
|
+
show_error = "[red]Already on the first page.[/red]"
|
|
431
|
+
|
|
397
432
|
else:
|
|
398
433
|
show_error = "[red]Invalid choice. Please enter 'n' (next), 'p' (prev), or 'q' (quit).[/red]"
|
|
434
|
+
|
|
399
435
|
else:
|
|
400
|
-
#
|
|
436
|
+
# Single page or no sessions left — nothing to navigate
|
|
401
437
|
break
|
|
402
438
|
|
|
403
439
|
|
|
@@ -1851,3 +1887,48 @@ def fetch_interactive_session_page(cl, workspace_id, page_num, limit, filter_sta
|
|
|
1851
1887
|
include_archived=archived,
|
|
1852
1888
|
verify=verify_ssl
|
|
1853
1889
|
)
|
|
1890
|
+
|
|
1891
|
+
|
|
1892
|
+
def build_app_filtered_page_fetcher(cl, workspace_id, limit, filter_status, filter_only_mine, archived, verify_ssl):
|
|
1893
|
+
"""Return a page-fetch callback that strips app-type sessions from each page.
|
|
1894
|
+
|
|
1895
|
+
The returned callable is intended for use as the ``fetch_page_callback``
|
|
1896
|
+
argument of :func:`create_interactive_session_list_table`. It fetches a
|
|
1897
|
+
page via :func:`fetch_interactive_session_page` and removes any session
|
|
1898
|
+
whose ``interactiveSessionType`` belongs to :data:`APP_SESSION_TYPES`
|
|
1899
|
+
before returning, ensuring app sessions never appear in the interactive
|
|
1900
|
+
table even when the user navigates to subsequent pages.
|
|
1901
|
+
|
|
1902
|
+
Parameters
|
|
1903
|
+
----------
|
|
1904
|
+
cl : Cloudos
|
|
1905
|
+
API client instance.
|
|
1906
|
+
workspace_id : str
|
|
1907
|
+
Team/workspace identifier.
|
|
1908
|
+
limit : int
|
|
1909
|
+
Page size.
|
|
1910
|
+
filter_status : tuple or None
|
|
1911
|
+
Status values to pass to the API filter.
|
|
1912
|
+
filter_only_mine : bool
|
|
1913
|
+
Restrict results to the authenticated user's sessions.
|
|
1914
|
+
archived : bool
|
|
1915
|
+
Include archived sessions.
|
|
1916
|
+
verify_ssl : bool or str
|
|
1917
|
+
SSL verification setting.
|
|
1918
|
+
|
|
1919
|
+
Returns
|
|
1920
|
+
-------
|
|
1921
|
+
callable
|
|
1922
|
+
A function ``fetch_page(page_num: int) -> dict`` that returns the API
|
|
1923
|
+
response dict with app sessions already removed from ``"sessions"``.
|
|
1924
|
+
"""
|
|
1925
|
+
def fetch_page(page_num):
|
|
1926
|
+
page_result = fetch_interactive_session_page(
|
|
1927
|
+
cl, workspace_id, page_num, limit, filter_status, filter_only_mine, archived, verify_ssl
|
|
1928
|
+
)
|
|
1929
|
+
page_result['sessions'] = [
|
|
1930
|
+
s for s in page_result.get('sessions', [])
|
|
1931
|
+
if s.get('interactiveSessionType') not in APP_SESSION_TYPES
|
|
1932
|
+
]
|
|
1933
|
+
return page_result
|
|
1934
|
+
return fetch_page
|