Skip to content

Commit

Permalink
[SCFD-4346] Add interface to force create new resource (#745)
Browse files Browse the repository at this point in the history
* Initial interface implementation based on the API draft

* Address comments

* Remove duplicate run check

* Fix black

* Add unit test

* Address comments

* Fix typo
  • Loading branch information
angranl-flex authored Feb 27, 2025
1 parent 2ed7ab8 commit 6075b15
Show file tree
Hide file tree
Showing 11 changed files with 1,058 additions and 42 deletions.
77 changes: 76 additions & 1 deletion flow360/cloud/flow360_requests.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
"""Requests module"""

from typing import List, Optional, Union
from datetime import datetime
from typing import Annotated, List, Optional, Union

import pydantic as pd_v2
import pydantic.v1 as pd
from pydantic.alias_generators import to_camel
from typing_extensions import Literal

from ..component.utils import is_valid_uuid

LengthUnitType = Literal["m", "mm", "cm", "inch", "ft"]


def _valid_id_validator(input_id: str):
is_valid_uuid(input_id)
return input_id


IDStringType = Annotated[str, pd_v2.AfterValidator(_valid_id_validator)]


###==== V1 API Payload definition ===###


Expand Down Expand Up @@ -153,3 +164,67 @@ class NewReportRequest(Flow360RequestsV2):
resources: List[_Resource]
config_json: str
solver_version: str


class DraftCreateRequest(Flow360RequestsV2):
"""Data model for draft create request"""

name: Optional[str] = pd.Field(None)
project_id: IDStringType = pd.Field()
source_item_id: IDStringType = pd.Field()
source_item_type: Literal[
"Project", "Folder", "Geometry", "SurfaceMesh", "VolumeMesh", "Case", "Draft"
] = pd.Field()
solver_version: str = pd.Field()
fork_case: bool = pd.Field()

@pd_v2.field_validator("name", mode="after")
@classmethod
def _generate_default_name(cls, values):
if values is None:
values = "Draft " + datetime.now().strftime("%m-%d %H:%M:%S")
return values


class ForceCreationConfig(Flow360RequestsV2):
"""Data model for force creation configuration"""

start_from: Literal["SurfaceMesh", "VolumeMesh", "Case"] = pd.Field()


class DraftRunRequest(Flow360RequestsV2):
"""Data model for draft run request"""

up_to: Literal["SurfaceMesh", "VolumeMesh", "Case"] = pd.Field()
use_in_house: bool = pd.Field()
force_creation_config: Optional[ForceCreationConfig] = pd.Field(
None,
)
source_item_type: Literal["Geometry", "SurfaceMesh", "VolumeMesh", "Case"] = pd.Field(
exclude=True
)

@pd_v2.model_validator(mode="after")
def _validate_force_creation_config(self):
# pylint: disable=no-member

order = {"Geometry": 0, "SurfaceMesh": 1, "VolumeMesh": 2, "Case": 3}
source_order = order[self.source_item_type]
up_to_order = order[self.up_to]

if self.force_creation_config is not None:
force_start_order = order[self.force_creation_config.start_from]
if (force_start_order <= source_order and self.source_item_type != "Case") or (
self.source_item_type == "Case" and self.force_creation_config.start_from != "Case"
):
raise ValueError(
f"Invalid force creation configuration: 'start_from' ({self.force_creation_config.start_from}) "
f"must be later than 'source_item_type' ({self.source_item_type})."
)

if force_start_order > up_to_order:
raise ValueError(
f"Invalid force creation configuration: 'start_from' ({self.force_creation_config.start_from}) "
f"cannot be later than 'up_to' ({self.up_to})."
)
return self
19 changes: 17 additions & 2 deletions flow360/component/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ def _run(
run_async: bool,
solver_version: str,
use_beta_mesher: bool,
**kwargs,
):
"""
Runs a simulation for the project.
Expand Down Expand Up @@ -982,19 +983,27 @@ def _run(
"\n>> Validation error found in the simulation params! Errors are: " + error_msg
)

source_item_type = self.metadata.root_item_type.value if fork_from is None else "Case"
start_from = kwargs.get("start_from", None)

draft = Draft.create(
name=draft_name,
project_id=self.metadata.id,
source_item_id=self.metadata.root_item_id if fork_from is None else fork_from.id,
source_item_type=(self.metadata.root_item_type.value if fork_from is None else "Case"),
source_item_type=source_item_type,
solver_version=solver_version if solver_version else self.solver_version,
fork_case=fork_from is not None,
).submit()

draft.update_simulation_params(params)

try:
destination_id = draft.run_up_to_target_asset(target, use_beta_mesher=use_beta_mesher)
destination_id = draft.run_up_to_target_asset(
target,
source_item_type=source_item_type,
use_beta_mesher=use_beta_mesher,
start_from=start_from,
)
except RuntimeError:
return None

Expand Down Expand Up @@ -1036,6 +1045,7 @@ def generate_surface_mesh(
run_async: bool = True,
solver_version: str = None,
use_beta_mesher: bool = False,
**kwargs,
):
"""
Runs the surface mesher for the project.
Expand Down Expand Up @@ -1069,6 +1079,7 @@ def generate_surface_mesh(
fork_from=None,
solver_version=solver_version,
use_beta_mesher=use_beta_mesher,
**kwargs,
)
return surface_mesh

Expand All @@ -1080,6 +1091,7 @@ def generate_volume_mesh(
run_async: bool = True,
solver_version: str = None,
use_beta_mesher: bool = False,
**kwargs,
):
"""
Runs the volume mesher for the project.
Expand Down Expand Up @@ -1116,6 +1128,7 @@ def generate_volume_mesh(
fork_from=None,
solver_version=solver_version,
use_beta_mesher=use_beta_mesher,
**kwargs,
)
return volume_mesh

Expand All @@ -1128,6 +1141,7 @@ def run_case(
fork_from: Case = None,
solver_version: str = None,
use_beta_mesher: bool = False,
**kwargs,
):
"""
Runs a case for the project.
Expand All @@ -1154,5 +1168,6 @@ def run_case(
fork_from=fork_from,
solver_version=solver_version,
use_beta_mesher=use_beta_mesher,
**kwargs,
)
return case
73 changes: 34 additions & 39 deletions flow360/component/simulation/web/draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,26 @@
from __future__ import annotations

import json
from datetime import datetime
from typing import Annotated, Literal, Optional

import pydantic as pd
from typing import Literal, Union

from flow360.cloud.flow360_requests import (
DraftCreateRequest,
DraftRunRequest,
ForceCreationConfig,
IDStringType,
)
from flow360.cloud.rest_api import RestApi
from flow360.component.interfaces import DraftInterface
from flow360.component.resource_base import (
AssetMetaBaseModel,
Flow360Resource,
ResourceDraft,
)
from flow360.component.utils import is_valid_uuid, validate_type
from flow360.component.utils import validate_type
from flow360.exceptions import Flow360WebError
from flow360.log import log


def _valid_id_validator(input_id: str):
is_valid_uuid(input_id)
return input_id


IDStringType = Annotated[str, pd.AfterValidator(_valid_id_validator)]


class DraftPostRequest(pd.BaseModel):
"""Data model for draft post request"""

name: Optional[str] = pd.Field(None)
project_id: IDStringType = pd.Field(serialization_alias="projectId")
source_item_id: IDStringType = pd.Field(serialization_alias="sourceItemId")
source_item_type: Literal[
"Project", "Folder", "Geometry", "SurfaceMesh", "VolumeMesh", "Case", "Draft"
] = pd.Field(serialization_alias="sourceItemType")
solver_version: str = pd.Field(serialization_alias="solverVersion")
fork_case: bool = pd.Field(serialization_alias="forkCase")

@pd.field_validator("name", mode="after")
@classmethod
def _generate_default_name(cls, values):
if values is None:
values = "Draft " + datetime.now().strftime("%m-%d %H:%M:%S")
return values


class DraftDraft(ResourceDraft):
"""
Draft Draft component
Expand All @@ -65,7 +40,7 @@ def __init__(
solver_version: str,
fork_case: bool,
):
self._request = DraftPostRequest(
self._request = DraftCreateRequest(
name=name,
project_id=project_id,
source_item_id=source_item_id,
Expand Down Expand Up @@ -142,18 +117,38 @@ def get_simulation_dict(self) -> dict:
response = self.get(method="simulation/file", params={"type": "simulation"})
return json.loads(response["simulationJson"])

def run_up_to_target_asset(self, target_asset: type, use_beta_mesher: bool) -> str:
def run_up_to_target_asset(
self,
target_asset: type,
use_beta_mesher: bool,
source_item_type: Literal["Geometry", "SurfaceMesh", "VolumeMesh", "Case"],
start_from: Union[None, Literal["SurfaceMesh", "VolumeMesh", "Case"]],
) -> str:
"""run the draft up to the target asset"""

try:
# pylint: disable=protected-access
if use_beta_mesher is True:
log.info("Selecting beta/in-house mesher for possible meshing tasks.")
if start_from:
if start_from != target_asset._cloud_resource_type_name:
log.info(
f"Force creating new resource(s) from {start_from} "
+ f"until {target_asset._cloud_resource_type_name}"
)
else:
log.info(f"Force creating a new {target_asset._cloud_resource_type_name}.")
force_creation_config = (
ForceCreationConfig(start_from=start_from) if start_from else None
)
run_request = DraftRunRequest(
source_item_type=source_item_type,
up_to=target_asset._cloud_resource_type_name,
use_in_house=use_beta_mesher,
force_creation_config=force_creation_config,
)
run_response = self.post(
json={
"upTo": target_asset._cloud_resource_type_name,
"useInHouse": use_beta_mesher,
},
run_request.model_dump(by_alias=True),
method="run",
)
destination_id = run_response["id"]
Expand Down
Loading

0 comments on commit 6075b15

Please # to comment.