Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 2 additions & 26 deletions flow360/cli/api_set_func.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
"""Helper function to set up the API key for the user."""

from click.testing import CliRunner

import flow360.user_config as user_config # pylint: disable=consider-using-from-import
from flow360.cli.app import configure
from flow360.log import log
from flow360.user_config import configure_apikey


def configure_caller(apikey: str, environment: str = None, profile: str = "default") -> None:
Expand All @@ -19,24 +15,4 @@ def configure_caller(apikey: str, environment: str = None, profile: str = "defau
Returns:
None
"""
runner = CliRunner()

# Construct CLI arguments as a list
args = ["--apikey", apikey, "--profile", profile]

if environment:
if environment.lower() in ("dev", "uat"):
args += ["--" + environment.lower()]
elif environment.lower() == "prod":
args += []
else:
args += ["--env", environment]

# Invoke the `configure` command
result = runner.invoke(configure, args)

if result.exit_code != 0:
log.error(result.output if result.output else str(result.exception))
else:
log.info("Configuration successful.")
user_config.UserConfig = user_config.BasicUserConfig() # Reload
configure_apikey(apikey=apikey, environment=environment, profile=profile)
37 changes: 36 additions & 1 deletion flow360/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,46 @@
"attr": "project",
"help": "Inspect and manage Flow360 projects.",
},
"draft": {
"module": "flow360.cli.draft",
"attr": "draft",
"help": "Inspect draft resources.",
},
"geometry": {
"module": "flow360.cli.assets",
"attr": "geometry",
"help": "Inspect Flow360 geometries.",
},
"surface-mesh": {
"module": "flow360.cli.assets",
"attr": "surface_mesh",
"help": "Inspect Flow360 surface meshes.",
},
"volume-mesh": {
"module": "flow360.cli.assets",
"attr": "volume_mesh",
"help": "Inspect Flow360 volume meshes.",
},
"case": {
"module": "flow360.cli.assets",
"attr": "case",
"help": "Inspect Flow360 cases.",
},
"folder": {
"module": "flow360.cli.folder",
"attr": "folder",
"help": "Inspect Flow360 folders.",
},
"open": {
"module": "flow360.cli.open_resource",
"attr": "open_resource",
"help": "Open a Flow360 resource in the browser.",
},
"wait": {
"module": "flow360.cli.wait",
"attr": "wait",
"help": "Wait for a Flow360 resource to reach a terminal state.",
},
}


Expand All @@ -47,7 +82,7 @@ class LazyFlow360Group(click.Group):
def invoke(self, ctx):
try:
return super().invoke(ctx)
except click.ClickException:
except (click.ClickException, click.exceptions.Exit, click.Abort):
raise
except Exception as error: # pylint: disable=broad-except
# Convert uncaught SDK auth failures into normal CLI errors.
Expand Down
299 changes: 299 additions & 0 deletions flow360/cli/assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
"""
Asset CLI commands.
"""

from __future__ import annotations

import json

import click

from flow360.cli.output import emit_json
from flow360.cli.resource_state import get_resource_state_for_type


def _serialize_asset_info(info):
return {
"id": info.get("id"),
"name": info.get("name"),
"project_id": info.get("projectId"),
"parent_id": info.get("parentId"),
"solver_version": info.get("solverVersion"),
"status": info.get("status"),
"tags": list(info.get("tags") or []),
"type": info.get("type"),
"created_at": info.get("createdAt"),
"updated_at": info.get("updatedAt"),
}


def _get_asset_info(webapi_cls, asset_id):
# pylint: disable=import-outside-toplevel
return webapi_cls(asset_id).get_info()


def _get_asset_simulation_json(webapi_cls, asset_id):
# pylint: disable=import-outside-toplevel
simulation_json = webapi_cls(asset_id).get_simulation_json()
if isinstance(simulation_json, str):
return json.loads(simulation_json)
return simulation_json


def _summarize_simulation_json(simulation_json):
# pylint: disable=import-outside-toplevel
from flow360.cli.simulation_summary import summarize_simulation

return summarize_simulation(simulation_json)


def _emit_asset_summary(webapi_cls, asset_id):
emit_json(
{
"id": asset_id,
"summary": _summarize_simulation_json(_get_asset_simulation_json(webapi_cls, asset_id)),
}
)


@click.group("geometry")
def geometry():
"""Inspect Flow360 geometries."""


def _emit_geometry_info(geometry_id):
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import GeometryWebApi

info = _get_asset_info(GeometryWebApi, geometry_id)
emit_json(_serialize_asset_info(info))


@geometry.command("info")
@click.argument("geometry_id")
def info_geometry(geometry_id):
"""Get geometry metadata."""
_emit_geometry_info(geometry_id)


@geometry.command("get", hidden=True)
@click.argument("geometry_id")
def get_geometry_alias(geometry_id):
"""Backward-compatible alias for geometry info."""
_emit_geometry_info(geometry_id)


@geometry.command("state")
@click.argument("geometry_id")
def state_geometry(geometry_id):
"""Get geometry lifecycle state."""
emit_json(get_resource_state_for_type("Geometry", geometry_id))


@geometry.command("summary")
@click.argument("geometry_id")
def summary_geometry(geometry_id):
"""Summarize geometry simulation settings."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import GeometryWebApi

_emit_asset_summary(GeometryWebApi, geometry_id)


@geometry.group("simulation")
def geometry_simulation():
"""Namespace for geometry simulation commands."""


@geometry_simulation.command("get")
@click.argument("geometry_id")
def get_geometry_simulation(geometry_id):
"""Get geometry simulation JSON."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import GeometryWebApi

emit_json({"simulation": _get_asset_simulation_json(GeometryWebApi, geometry_id)})


@click.group("surface-mesh")
def surface_mesh():
"""Inspect Flow360 surface meshes."""


def _emit_surface_mesh_info(surface_mesh_id):
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import SurfaceMeshWebApi

info = _get_asset_info(SurfaceMeshWebApi, surface_mesh_id)
emit_json(_serialize_asset_info(info))


@surface_mesh.command("info")
@click.argument("surface_mesh_id")
def info_surface_mesh(surface_mesh_id):
"""Get surface mesh metadata."""
_emit_surface_mesh_info(surface_mesh_id)


@surface_mesh.command("get", hidden=True)
@click.argument("surface_mesh_id")
def get_surface_mesh_alias(surface_mesh_id):
"""Backward-compatible alias for surface mesh info."""
_emit_surface_mesh_info(surface_mesh_id)


@surface_mesh.command("state")
@click.argument("surface_mesh_id")
def state_surface_mesh(surface_mesh_id):
"""Get surface mesh lifecycle state."""
emit_json(get_resource_state_for_type("SurfaceMesh", surface_mesh_id))


@surface_mesh.command("summary")
@click.argument("surface_mesh_id")
def summary_surface_mesh(surface_mesh_id):
"""Summarize surface mesh simulation settings."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import SurfaceMeshWebApi

_emit_asset_summary(SurfaceMeshWebApi, surface_mesh_id)


@surface_mesh.group("simulation")
def surface_mesh_simulation():
"""Namespace for surface mesh simulation commands."""


@surface_mesh_simulation.command("get")
@click.argument("surface_mesh_id")
def get_surface_mesh_simulation(surface_mesh_id):
"""Get surface mesh simulation JSON."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import SurfaceMeshWebApi

emit_json({"simulation": _get_asset_simulation_json(SurfaceMeshWebApi, surface_mesh_id)})


@click.group("volume-mesh")
def volume_mesh():
"""Inspect Flow360 volume meshes."""


def _emit_volume_mesh_info(volume_mesh_id):
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import VolumeMeshWebApi

info = _get_asset_info(VolumeMeshWebApi, volume_mesh_id)
emit_json(_serialize_asset_info(info))


@volume_mesh.command("info")
@click.argument("volume_mesh_id")
def info_volume_mesh(volume_mesh_id):
"""Get volume mesh metadata."""
_emit_volume_mesh_info(volume_mesh_id)


@volume_mesh.command("get", hidden=True)
@click.argument("volume_mesh_id")
def get_volume_mesh_alias(volume_mesh_id):
"""Backward-compatible alias for volume mesh info."""
_emit_volume_mesh_info(volume_mesh_id)


@volume_mesh.command("state")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@click.argument("volume_mesh_id")
def state_volume_mesh(volume_mesh_id):
"""Get volume mesh lifecycle state."""
emit_json(get_resource_state_for_type("VolumeMesh", volume_mesh_id))


@volume_mesh.command("summary")
@click.argument("volume_mesh_id")
def summary_volume_mesh(volume_mesh_id):
"""Summarize volume mesh simulation settings."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import VolumeMeshWebApi

_emit_asset_summary(VolumeMeshWebApi, volume_mesh_id)


@volume_mesh.group("simulation")
def volume_mesh_simulation():
"""Namespace for volume mesh simulation commands."""


@volume_mesh_simulation.command("get")
@click.argument("volume_mesh_id")
def get_volume_mesh_simulation(volume_mesh_id):
"""Get volume mesh simulation JSON."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import VolumeMeshWebApi

emit_json({"simulation": _get_asset_simulation_json(VolumeMeshWebApi, volume_mesh_id)})


@click.group("case")
def case():
"""Inspect Flow360 cases."""


def _serialize_case_info(info):
payload = _serialize_asset_info(info)
payload["type"] = payload["type"] or "Case"
payload["mesh_id"] = info.get("caseMeshId") or info.get("meshId")
return payload


def _emit_case_info(case_id):
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import CaseWebApi

info = _get_asset_info(CaseWebApi, case_id)
emit_json(_serialize_case_info(info))


@case.command("info")
@click.argument("case_id")
def info_case(case_id):
"""Get case metadata."""
_emit_case_info(case_id)


@case.command("get", hidden=True)
@click.argument("case_id")
def get_case_alias(case_id):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

"""Backward-compatible alias for case info."""
_emit_case_info(case_id)


@case.command("state")
@click.argument("case_id")
def state_case(case_id):
"""Get case lifecycle state."""
emit_json(get_resource_state_for_type("Case", case_id))


@case.command("summary")
@click.argument("case_id")
def summary_case(case_id):
"""Summarize case simulation settings."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import CaseWebApi

_emit_asset_summary(CaseWebApi, case_id)


@case.group("simulation")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())

def case_simulation():
"""Namespace for case simulation commands."""


@case_simulation.command("get")
@click.argument("case_id")
def get_case_simulation(case_id):
"""Get case simulation JSON."""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.web.asset_webapi import CaseWebApi

emit_json({"simulation": _get_asset_simulation_json(CaseWebApi, case_id)})
Loading
Loading