Skip to content

Commit

Permalink
Added interface to show all of user's projects filtered by project na…
Browse files Browse the repository at this point in the history
…me (#654)

* Rebased

* Added shorthand
  • Loading branch information
benflexcompute authored Jan 15, 2025
1 parent 5659e76 commit 2297e7b
Show file tree
Hide file tree
Showing 12 changed files with 985 additions and 14 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/codestyle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
python-version: '3.9'
cache: 'poetry'
- name: Install black
run: poetry install --only=dev
run: poetry install
- name: Run black
run: poetry run black . --check

Expand All @@ -39,7 +39,7 @@ jobs:
python-version: '3.9'
cache: 'poetry'
- name: Install isort
run: poetry install --only=dev
run: poetry install
- name: Check isort version
run: poetry run isort --version
- name: Run isort
Expand Down
14 changes: 12 additions & 2 deletions examples/tutorials/notebook/notebook_tutorial_2D_crm.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,12 @@
" operating_condition_params = fl.SimulationParams(\n",
" # Create operating conditions using mach and reynolds number\n",
" operating_condition=fl.operating_condition_from_mach_reynolds(\n",
" mach=0.2, reynolds=5e6, temperature=272.1, alpha=16 * u.deg, beta=0 * u.deg, project_length_unit=1 * u.m\n",
" mach=0.2,\n",
" reynolds=5e6,\n",
" temperature=272.1,\n",
" alpha=16 * u.deg,\n",
" beta=0 * u.deg,\n",
" project_length_unit=1 * u.m,\n",
" ),\n",
" )"
]
Expand Down Expand Up @@ -1642,7 +1647,12 @@
" moment_center=[0.25, 0, 0], moment_length=[1, 1, 1], area=0.01\n",
" ),\n",
" operating_condition=fl.operating_condition_from_mach_reynolds(\n",
" mach=0.2, reynolds=5e6, temperature=272.1, alpha=16 * u.deg, beta=0 * u.deg, project_length_unit=1 * u.m\n",
" mach=0.2,\n",
" reynolds=5e6,\n",
" temperature=272.1,\n",
" alpha=16 * u.deg,\n",
" beta=0 * u.deg,\n",
" project_length_unit=1 * u.m,\n",
" ),\n",
" time_stepping=fl.Steady(\n",
" max_steps=3000, CFL=fl.RampCFL(initial=20, final=300, ramp_steps=500)\n",
Expand Down
13 changes: 13 additions & 0 deletions flow360/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import toml

from flow360.cli import dict_utils
from flow360.component.project_utils import show_projects_with_keyword_filter

home = expanduser("~")
# pylint: disable=invalid-name
Expand Down Expand Up @@ -85,4 +86,16 @@ def configure(apikey, profile, dev, suppress_submit_warning, beta_features):
click.echo("done.")


# For displaying all projects
@click.command("show_projects", context_settings={"show_default": True})
@click.option("--keyword", "-k", help="Filter projects by keyword", default=None, type=str)
def show_projects(keyword):
"""
Display all available projects with optional keyword filter.
"""

show_projects_with_keyword_filter(search_keyword=keyword)


flow360.add_command(configure)
flow360.add_command(show_projects)
13 changes: 13 additions & 0 deletions flow360/component/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ProjectInterface,
VolumeMeshInterfaceV2,
)
from flow360.component.project_utils import show_projects_with_keyword_filter
from flow360.component.resource_base import Flow360Resource
from flow360.component.simulation.entity_info import GeometryEntityInfo
from flow360.component.simulation.outputs.output_entities import (
Expand Down Expand Up @@ -418,6 +419,18 @@ class Project(pd.BaseModel):
_project_webapi: Optional[RestApi] = pd.PrivateAttr(None)
_root_simulation_json: Optional[dict] = pd.PrivateAttr(None)

@classmethod
def show_remote(cls, search_keyword: Union[None, str] = None):
"""
Shows all projects on the cloud.
Parameters
----------
search_keyword : str, optional
"""
show_projects_with_keyword_filter(search_keyword)

@property
def id(self) -> str:
"""
Expand Down
119 changes: 119 additions & 0 deletions flow360/component/project_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Support class and functions for project interface.
"""

import datetime
from typing import List, Literal, Optional

import pydantic as pd

from flow360.cloud.rest_api import RestApi
from flow360.component.interfaces import ProjectInterface
from flow360.component.utils import parse_datetime
from flow360.log import log


class AssetStatistics(pd.BaseModel):
"""Statistics for an asset"""

count: int
successCount: int
runningCount: int
divergedCount: int
errorCount: int


class ProjectStatistics(pd.BaseModel):
"""Statistics for a project"""

geometry: Optional[AssetStatistics] = pd.Field(None, alias="Geometry")
surface_mesh: Optional[AssetStatistics] = pd.Field(None, alias="SurfaceMesh")
volume_mesh: Optional[AssetStatistics] = pd.Field(None, alias="VolumeMesh")
case: Optional[AssetStatistics] = pd.Field(None, alias="Case")


class ProjectInfo(pd.BaseModel):
"""Information about a project, retrieved from the projects get API"""

name: str
project_id: str = pd.Field(alias="id")
tags: list[str] = pd.Field()
description: str = pd.Field()
statistics: ProjectStatistics = pd.Field()
solver_version: Optional[str] = pd.Field(
None, alias="solverVersion", description="If None then the project is from old database"
)
created_at: str = pd.Field(alias="createdAt")
root_item_type: Literal["Geometry", "SurfaceMesh", "VolumeMesh"] = pd.Field(
alias="rootItemType"
)

@pd.computed_field
@property
def local_time_zone_created_time(self) -> datetime.datetime:
"""Convert string time to datetime obj and also convert to local time zone"""
return parse_datetime(self.created_at)


class ProjectRecords(pd.BaseModel):
"""Holds all records of a user's project"""

records: List[ProjectInfo] = pd.Field()

def __str__(self):
"""Print out all info about the project"""
if not self.records:
output_str = "No matching projects found. Try skip naming patterns to show all."
return output_str
output_str = ">>> Projects sorted by creation time:\n"
# pylint: disable=not-an-iterable
for item in self.records:
output_str += f" Name: {item.name}\n"
output_str += f" Created at: {item.local_time_zone_created_time.strftime('%Y-%m-%d %H:%M %Z')}\n"
output_str += f" Created with: {item.root_item_type}\n"
output_str += f" ID: {item.project_id}\n"
output_str += (
f" Link: https://flow360.simulation.cloud/workbench/{item.project_id}\n"
)
if item.tags:
output_str += f" Tags: {item.tags}\n"
if item.description:
output_str += f" Description: {item.description}\n"
if item.statistics.geometry:
output_str += f" Geometry count: {item.statistics.geometry.count}\n"
if item.statistics.surface_mesh:
output_str += f" Surface Mesh count: {item.statistics.surface_mesh.count}\n"
if item.statistics.volume_mesh:
output_str += f" Volume Mesh count: {item.statistics.volume_mesh.count}\n"
if item.statistics.case:
output_str += f" Case count: {item.statistics.case.count}\n"

output_str += "\n"
return output_str


def show_projects_with_keyword_filter(search_keyword: str):
"""Show all projects with a keyword filter"""
# pylint: disable=invalid-name
MAX_DISPLAYABLE_ITEM_COUNT = 200
MAX_SEARCHABLE_ITEM_COUNT = 1000
_api = RestApi(ProjectInterface.endpoint, id=None)
resp = _api.get(
params={
"page": "0",
"size": MAX_SEARCHABLE_ITEM_COUNT,
"filterKeywords": search_keyword,
"sortFields": ["createdAt"],
"sortDirections": ["asc"],
}
)

all_projects = ProjectRecords.model_validate({"records": resp["records"]})
log.info("%s", str(all_projects))

if resp["total"] > MAX_DISPLAYABLE_ITEM_COUNT:
log.warning(
f"Total number of projects matching the keyword on the cloud is {resp['total']}, "
f"but only the latest {MAX_DISPLAYABLE_ITEM_COUNT} will be displayed. "
)
log.info("Total number of matching projects on the cloud: %d", resp["total"])
10 changes: 2 additions & 8 deletions flow360/component/simulation/framework/entity_registry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Registry for managing and storing instances of various entity types."""

import re
from typing import Any, Dict, Union

import pydantic as pd
Expand All @@ -11,6 +10,7 @@
MergeConflictError,
_merge_objects,
)
from flow360.component.utils import _naming_pattern_handler
from flow360.log import log


Expand Down Expand Up @@ -109,13 +109,7 @@ def find_by_naming_pattern(
List[EntityBase]: A list of entities whose names match the pattern.
"""
matched_entities = []
if "*" in pattern:
# Convert wildcard to regex pattern
regex_pattern = "^" + pattern.replace("*", ".*") + "$"
else:
regex_pattern = f"^{pattern}$" # Exact match if no '*'

regex = re.compile(regex_pattern)
regex = _naming_pattern_handler(pattern=pattern)
# pylint: disable=no-member
for entity_list in self.internal_registry.values():
matched_entities.extend(filter(lambda x: regex.match(x.name), entity_list))
Expand Down
17 changes: 17 additions & 0 deletions flow360/component/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,20 @@ def _local_download_file(
shutil.copy(expected_local_file, new_local_file)

return _local_download_file


def _naming_pattern_handler(pattern: str) -> re.Pattern[str]:
"""
Handler of the user supplied naming pattern.
This enables both glob pattern and regexp pattern.
If "*" is found in the pattern, it will be treated as a glob pattern.
"""

if "*" in pattern:
# Convert wildcard to regex pattern
regex_pattern = "^" + pattern.replace("*", ".*") + "$"
else:
regex_pattern = f"^{pattern}$" # Exact match if no '*'

regex = re.compile(regex_pattern)
return regex
Loading

0 comments on commit 2297e7b

Please # to comment.