Add Flow360 CLI resource inspection commands#2027
Conversation
cd24dfe to
ab7ff50
Compare
Coverage report (flow360)Click to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 62f5159. Configure here.
| cleaned[key] = cleaned_child | ||
| return dict(cleaned) | ||
| if isinstance(value, list): | ||
| return [_clean_empty(item) for item in value if item not in ({}, [], None)] |
There was a problem hiding this comment.
_clean_empty list branch keeps items that become empty
Low Severity
The list branch of _clean_empty filters items before recursively cleaning them, unlike the dict branch which filters after cleaning. A list item like {"nested": {}} passes the raw not in ({}, [], None) check, but _clean_empty then reduces it to {}, which remains in the output. The dict branch correctly applies _clean_empty(child) first and then checks if cleaned_child in ({}, [], None). The list comprehension on line 436 needs to clean items first and then filter the results.
Reviewed by Cursor Bugbot for commit 62f5159. Configure here.
|
|
||
| @case.command("get", hidden=True) | ||
| @click.argument("case_id") | ||
| def get_case_alias(case_id): |
| _emit_volume_mesh_info(volume_mesh_id) | ||
|
|
||
|
|
||
| @volume_mesh.command("state") |
There was a problem hiding this comment.
Is there a way to not having to list one by one but define a base Group to define all the shared/common functions? It seems that a lot of the code here are very similar and just operates on different assets.
| _emit_asset_summary(CaseWebApi, case_id) | ||
|
|
||
|
|
||
| @case.group("simulation") |
There was a problem hiding this comment.
I feel "simulation" is not a very intuitive representation for simulation.json?
A side question I guess is how is this going to be used? The user has to know we have a dedicated function (non-Pydantic native) to deserialize the acquired JSON. (services.py --> validate_model())
| f"{resource_ref.resource_type} {resource_ref.id} does not expose a projectId." | ||
| ) | ||
| path = _get_workbench_path(project_id, resource_ref.id, resource_ref.resource_type) | ||
| url = Env.current.get_web_real_url(path) |
There was a problem hiding this comment.
Where is the entry to set the env here?
| @draft.command("get", hidden=True) | ||
| @click.argument("draft_id") | ||
| def get_draft_alias(draft_id): | ||
| """Backward-compatible alias for draft info.""" |
There was a problem hiding this comment.
Same here, is this AI hallucination?
| _emit_draft_list(project_id) | ||
|
|
||
|
|
||
| @draft.command("ls", hidden=True) |
| previous_disable_level = logging.root.manager.disable | ||
| logging.disable(logging.WARNING) | ||
| try: | ||
| params_dict = SimulationParams._sanitize_params_dict( # pylint: disable=protected-access |
There was a problem hiding this comment.
Why do we want to go through updater and deserialization here? Why not show simulation_json as is?
| from flow360.cli.resource_refs import ResourceRefError, parse_resource_ref | ||
|
|
||
| SUCCESS_STATES = {"completed", "processed"} | ||
| TERMINAL_STATES = SUCCESS_STATES | {"failed", "error", "deleted"} |


Summary
Added commands
flow360 geometry info <geometry_id>flow360 geometry state <geometry_id>flow360 geometry simulation get <geometry_id>simulation.json.flow360 geometry summary <geometry_id>flow360 surface-mesh info <surface_mesh_id>flow360 surface-mesh state <surface_mesh_id>flow360 surface-mesh simulation get <surface_mesh_id>simulation.json.flow360 surface-mesh summary <surface_mesh_id>flow360 volume-mesh info <volume_mesh_id>flow360 volume-mesh state <volume_mesh_id>flow360 volume-mesh simulation get <volume_mesh_id>simulation.json.flow360 volume-mesh summary <volume_mesh_id>flow360 case info <case_id>flow360 case state <case_id>flow360 case simulation get <case_id>simulation.json.flow360 case summary <case_id>flow360 draft list --project-id <project_id>flow360 draft info <draft_id>flow360 draft state <draft_id>flow360 draft simulation get <draft_id>simulation.json.flow360 open <ref_id>flow360 wait <ref_id>Hidden compatibility aliases remain for transitional use:
getaliasesinfo, anddraft lsaliasesdraft list.Local command timings
Measured on 2026-05-04 with the installed console script directly, not through
poetry run, becausepoetry runadds local Poetry interpreter-selection overhead. API timings use live production resources and include network/backend latency. Browser-open timings were run withBROWSER=trueto avoid opening a local browser while still exercising URL resolution.Resources used: Simple Airplane project
prj-7cd91cca-3049-47cd-a5c8-d7cf0ad1cbc0, geometrygeo-55cf9c04-02e2-4584-870d-9352d397a789, surface meshsm-1a308cad-f76d-4ef7-a908-9b4cc5be50e0, volume meshvm-e84a7911-2370-406f-99a8-fc0a35a9b87a, casecase-7b77792d-ed36-4760-921a-a7b9614a867d, draftdft-5bf5de98-9597-457e-95e7-067380870080.flow360 draft --helpflow360 draft simulation --helpflow360 geometry --helpflow360 geometry simulation --helpflow360 surface-mesh --helpflow360 surface-mesh simulation --helpflow360 volume-mesh --helpflow360 volume-mesh simulation --helpflow360 case --helpflow360 case simulation --helpflow360 open --helpflow360 wait --helpflow360 draft list --project-id <project_id>flow360 draft info <draft_id>flow360 draft state <draft_id>flow360 draft simulation get <draft_id>flow360 geometry info <geometry_id>flow360 geometry state <geometry_id>flow360 geometry summary <geometry_id>flow360 geometry simulation get <geometry_id>flow360 surface-mesh info <surface_mesh_id>flow360 surface-mesh state <surface_mesh_id>flow360 surface-mesh summary <surface_mesh_id>flow360 surface-mesh simulation get <surface_mesh_id>flow360 volume-mesh info <volume_mesh_id>flow360 volume-mesh state <volume_mesh_id>flow360 volume-mesh summary <volume_mesh_id>flow360 volume-mesh simulation get <volume_mesh_id>flow360 case info <case_id>flow360 case state <case_id>flow360 case summary <case_id>flow360 case simulation get <case_id>flow360 open <project_id>flow360 open <case_id>flow360 open <folder_id>flow360 wait <case_id>Example outputs
flow360 case info case-...{ "id": "case-...", "name": "Alpha -18", "project_id": "prj-...", "parent_id": null, "solver_version": "release-25.9", "status": "completed", "tags": [], "type": "Case", "created_at": "2026-04-01T12:00:00Z", "updated_at": "2026-04-01T12:30:00Z", "mesh_id": "vm-..." }flow360 volume-mesh state vm-...{ "id": "vm-...", "type": "VolumeMesh", "status": "completed", "is_terminal": true, "is_success": true, "updated_at": "2026-04-01T12:30:00Z" }flow360 case simulation get case-7b77792d-ed36-4760-921a-a7b9614a867dexcerpt{ "simulation": { "version": "25.8.3", "unit_system": {"name": "SI"}, "meshing": { "type_name": "MeshingParams", "defaults": { "boundary_layer_first_layer_thickness": {"units": "mm", "value": 0.01}, "surface_max_edge_length": {"units": "m", "value": 0.2} } }, "models": [ {"type": "Fluid", "turbulence_model_solver": {"type_name": "SpalartAllmaras"}}, {"name": "Wall", "type": "Wall"}, {"name": "Freestream", "type": "Freestream"} ], "operating_condition": { "alpha": {"units": "degree", "value": 10}, "beta": {"units": "degree", "value": 0}, "type_name": "AerospaceCondition", "velocity_magnitude": {"units": "m/s", "value": 100} }, "time_stepping": {"max_steps": 2000, "type_name": "Steady"} } }flow360 case summary case-7b77792d-ed36-4760-921a-a7b9614a867d{ "id": "case-7b77792d-ed36-4760-921a-a7b9614a867d", "summary": { "meshing": { "defaults": { "boundary_layer_first_layer_thickness": {"units": "mm", "value": 0.01}, "surface_max_edge_length": {"units": "m", "value": 0.2} }, "type_name": "MeshingParams" }, "models": [ {"type": "Fluid"}, { "entities": {"_count": 3, "_sample": ["leftWing", "fuselage", "rightWing"]}, "type": "Wall" }, {"type": "Freestream"} ], "operating_condition": { "alpha": {"units": "degree", "value": 10}, "type_name": "AerospaceCondition", "velocity_magnitude": {"units": "m/s", "value": 100} }, "outputs": [ { "entities": {"_count": 3, "_sample": ["leftWing", "fuselage", "rightWing"]}, "output_type": "SurfaceOutput" } ], "run_control": {"type_name": "RunControl"}, "time_stepping": {"type_name": "Steady"} } }flow360 draft list --project-id prj-...{ "records": [ { "id": "dft-...", "name": "Draft 1", "project_id": "prj-...", "solver_version": "release-25.9", "source_item_id": "geo-...", "source_item_type": "Geometry", "fork_case": false, "type": "Draft" } ] }flow360 open case-...{ "id": "case-...", "type": "Case", "url": "https://flow360.simulation.cloud/workbench/prj-...?id=case-...&type=Case", "opened": false }flow360 wait case-...{ "id": "case-...", "type": "Case", "status": "completed", "is_terminal": true, "is_success": true, "updated_at": "2026-04-01T12:30:00Z", "mesh_id": "vm-..." }Verification
Note
Medium Risk
Adds multiple new CLI entrypoints plus new lightweight V2 web-API wrappers and simulation-summary logic; risk is mainly around correctness of resource type parsing/URL resolution and state polling behavior, not security-critical changes.
Overview
Adds new Flow360 CLI resource-inspection surface. Introduces
geometry,surface-mesh,volume-mesh,case, anddraftcommand groups to fetch metadata (info), lifecycle state (state), and simulation payloads (simulation get), plus asummaryview that compacts/prunes simulation JSON.Adds cross-cutting CLI helpers and commands. Implements typed resource ref parsing (
resource_refs), browser URL resolution +flow360 open, and generic polling viaflow360 waitwith proper exit codes/timeouts; also refactors project tree building to use a lightweightbuild_project_treehelper (shared with the SDK) instead of importing the full project SDK.Improves auth/config ergonomics. Centralizes missing-API-key guidance messaging, adds
user_config.configure_apikey+reload_user_config(used by login/configure callers), normalizes stored environment names, and broadensLazyFlow360Groupexception passthrough for clean CLI exits.Reviewed by Cursor Bugbot for commit 62f5159. Bugbot is set up for automated code reviews on this repo. Configure here.