Skip to content
Merged
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
50 changes: 49 additions & 1 deletion flow360/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

import click
import toml
from packaging.version import InvalidVersion, Version

from flow360.cli import dict_utils
from flow360.component.project_utils import show_projects_with_keyword_filter
from flow360.environment import Env
from flow360.version import __version__

home = expanduser("~")
# pylint: disable=invalid-name
Expand Down Expand Up @@ -113,7 +115,7 @@ def configure(apikey, profile, dev, uat, env, suppress_submit_warning, beta_feat
@click.option("--env", prompt=False, default=None, help="The environment used for the query.")
def show_projects(keyword, env: str):
"""
Display all available projects with optional keyword filter.
Display all available projects with optional filter.
"""
prev_env_config = None
if env:
Expand All @@ -127,5 +129,51 @@ def show_projects(keyword, env: str):
prev_env_config.active()


@click.command("version")
def version():
"""
Display the version of the flow360 client,
plus the latest stable and beta versions available on PyPI.
"""
click.echo(f"Installed version: {__version__}")

# pylint: disable=import-outside-toplevel
from requests import RequestException, get

try:
resp = get("https://pypi.org/pypi/flow360/json", timeout=5)
resp.raise_for_status()
data = resp.json()

# Parse all release strings into Version objects
parsed = []
for v in data["releases"].keys():
try:
parsed.append(Version(v))
except InvalidVersion:
# skip any oddly formatted tags
continue

# Sort descending so the first non-prerelease is the latest stable,
# and the first prerelease is the latest beta
parsed.sort(reverse=True)

latest_stable = next((v for v in parsed if not v.is_prerelease), None)
latest_beta = next((v for v in parsed if v.is_prerelease), None)

if latest_stable:
click.echo(f"Latest stable on PyPI: {latest_stable}")
else:
click.echo("No stable releases found on PyPI.")

if latest_beta:
click.echo(f"Latest beta on PyPI: {latest_beta}")
else:
click.echo("No beta releases found on PyPI.")
except RequestException:
pass


flow360.add_command(configure)
flow360.add_command(show_projects)
flow360.add_command(version)
5 changes: 2 additions & 3 deletions flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.utils import model_attribute_unlock
from flow360.component.simulation.web.asset_base import AssetBase
from flow360.component.utils import parse_datetime
from flow360.exceptions import Flow360ConfigurationError
from flow360.log import log
Expand Down Expand Up @@ -211,7 +210,7 @@ def _set_up_params_non_persistent_entity_info(entity_info, params: SimulationPar


def _set_up_default_geometry_accuracy(
root_asset: AssetBase,
root_asset,
params: SimulationParams,
use_geometry_AI: bool, # pylint: disable=invalid-name
):
Expand Down Expand Up @@ -246,7 +245,7 @@ def _set_up_default_reference_geometry(params: SimulationParams, length_unit: Le


def set_up_params_for_uploading(
root_asset: AssetBase,
root_asset,
length_unit: LengthType,
params: SimulationParams,
use_beta_mesher: bool,
Expand Down
21 changes: 17 additions & 4 deletions flow360/component/simulation/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,22 +249,31 @@ class ValidationCalledBy(Enum):

def get_forward_compatibility_error_message(self, version_from: str, version_to: str):
"""
Return error message string indicating that the forward compatability is not guaranteed.
Return error message string indicating that the forward compatibility is not guaranteed.
"""
error_suffix = " Errors may occur since forward compatibility is limited."
if self == ValidationCalledBy.LOCAL:
return {
"type": (f"{version_from} > {version_to}"),
"loc": [],
"msg": "The cloud `SimulationParam` is too new for your local Python client."
"msg": "The cloud `SimulationParam` (version: "
+ version_from
+ ") is too new for your local Python client (version: "
+ version_to
+ ")."
+ error_suffix,
"ctx": {},
}
if self == ValidationCalledBy.SERVICE:
return {
"type": (f"{version_from} > {version_to}"),
"loc": [],
"msg": "Your `SimulationParams` is too new for the solver." + error_suffix,
"msg": "Your `SimulationParams` (version: "
+ version_from
+ ") is too new for the solver (version: "
+ version_to
+ ")."
+ error_suffix,
"ctx": {},
}
if self == ValidationCalledBy.PIPELINE:
Expand All @@ -274,7 +283,11 @@ def get_forward_compatibility_error_message(self, version_from: str, version_to:
# pylint:disable = protected-access
"type": (f"{version_from} > {version_to}"),
"loc": [],
"msg": "[Internal] Your `SimulationParams` is too new for the solver."
"msg": "[Internal] Your `SimulationParams` (version: "
+ version_from
+ ") is too new for the solver (version: "
+ version_to
+ ")."
+ error_suffix,
"ctx": {},
}
Expand Down
24 changes: 22 additions & 2 deletions flow360/component/simulation/web/asset_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
from abc import ABCMeta, abstractmethod
from typing import List, Optional, Union

from pydantic import ValidationError
from requests.exceptions import HTTPError

from flow360.cloud.flow360_requests import LengthUnitType
from flow360.cloud.rest_api import RestApi
from flow360.component.interfaces import BaseInterface, ProjectInterface
from flow360.component.project_utils import formatting_validation_errors
from flow360.component.resource_base import (
AssetMetaBaseModelV2,
Flow360Resource,
Expand All @@ -23,14 +25,20 @@
EntityInfoModel,
parse_entity_info_model,
)
from flow360.component.simulation.framework.updater_utils import Flow360Version
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.utils import (
_local_download_overwrite,
remove_properties_by_name,
validate_type,
)
from flow360.exceptions import Flow360ValidationError, Flow360WebError
from flow360.exceptions import (
Flow360RuntimeError,
Flow360ValidationError,
Flow360WebError,
)
from flow360.log import log
from flow360.version import __version__


class AssetBase(metaclass=ABCMeta):
Expand Down Expand Up @@ -117,7 +125,19 @@ def _from_supplied_entity_info(
entity_info_dict = asset_cache["project_entity_info"]
entity_info_dict = remove_properties_by_name(entity_info_dict, "_id")
# pylint: disable=protected-access
asset_obj._entity_info = parse_entity_info_model(entity_info_dict)
try:
asset_obj._entity_info = parse_entity_info_model(entity_info_dict)
except ValidationError as e:
cloud_version_str = SimulationParams._get_version_from_dict(model_dict=simulation_dict)
if Flow360Version(cloud_version_str) > Flow360Version(__version__):
raise Flow360RuntimeError(
"The cloud `SimulationParam` (version: "
+ cloud_version_str
+ ") is too new for your local Python client (version: "
+ __version__
+ ") and validation error occurred. Please update your local Python client.\nError:"
+ formatting_validation_errors(errors=e.errors())
) from None
return asset_obj

@classmethod
Expand Down
12 changes: 7 additions & 5 deletions tests/simulation/service/test_services_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,8 @@ def get_validation_levels_to_use(root_item_type, requested_levels):

def test_forward_compatibility_error():

from flow360.version import __version__

# Mock a future simulation.json
with open("data/updater_should_pass.json", "r") as fp:
future_dict = json.load(fp)
Expand All @@ -1114,9 +1116,9 @@ def test_forward_compatibility_error():
)

assert errors[0] == {
"type": "99.99.99 > 25.5.4",
"type": f"99.99.99 > {__version__}",
"loc": [],
"msg": "The cloud `SimulationParam` is too new for your local Python client. "
"msg": f"The cloud `SimulationParam` (version: 99.99.99) is too new for your local Python client (version: {__version__}). "
"Errors may occur since forward compatibility is limited.",
"ctx": {},
}
Expand All @@ -1128,16 +1130,16 @@ def test_forward_compatibility_error():
)

assert errors[0] == {
"type": "99.99.99 > 25.5.4",
"type": f"99.99.99 > {__version__}",
"loc": [],
"msg": "[Internal] Your `SimulationParams` is too new for the solver. Errors may occur since forward compatibility is limited.",
"msg": f"[Internal] Your `SimulationParams` (version: 99.99.99) is too new for the solver (version: {__version__}). Errors may occur since forward compatibility is limited.",
"ctx": {},
}

with pytest.raises(
ValueError,
match=re.escape(
"Your `SimulationParams` is too new for the solver. Errors may occur since forward compatibility is limited."
f"Your `SimulationParams` (version: 99.99.99) is too new for the solver (version: {__version__}). Errors may occur since forward compatibility is limited."
),
):
_, _, _ = services.generate_process_json(
Expand Down
Loading