Skip to content

Commit

Permalink
Added interface to show all of user's projects filtered by project name
Browse files Browse the repository at this point in the history
  • Loading branch information
benflexcompute committed Jan 14, 2025
1 parent c8c9430 commit 1ea562a
Show file tree
Hide file tree
Showing 8 changed files with 928 additions and 11 deletions.
14 changes: 14 additions & 0 deletions flow360/component/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@
SUPPORTED_GEOMETRY_FILE_PATTERNS,
MeshNameParser,
ProjectAssetCache,
ProjectRecords,
match_file_pattern,
)
from flow360.component.volume_mesh import VolumeMeshV2
from flow360.exceptions import Flow360FileError, Flow360ValueError, Flow360WebError
from flow360.log import log
from flow360.version import __solver_version__

AssetOrResource = Union[type[AssetBase], type[Flow360Resource]]
Expand Down Expand Up @@ -162,6 +164,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, project_naming_pattern: Union[None, str] = None):
"""
Shows all projects on the cloud. Use naming pattern filter if provided by user.
"""
_api = RestApi(ProjectInterface.endpoint, id=None)
resp = _api.get()
all_projects = ProjectRecords.model_validate({"records": resp["records"]})
if project_naming_pattern:
all_projects.filter_by_project_name_pattern(project_naming_pattern)
log.info(str(all_projects))

@property
def id(self) -> str:
"""
Expand Down
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
105 changes: 104 additions & 1 deletion flow360/component/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
Utility functions
"""

import datetime
import itertools
import os
import re
import shutil
from enum import Enum
from functools import wraps
from tempfile import NamedTemporaryFile
from typing import Generic, Iterable, Literal, Protocol, TypeVar
from typing import Generic, Iterable, List, Literal, Optional, Protocol, TypeVar

import pydantic as pd
import zstandard as zstd

from ..accounts_utils import Accounts
Expand Down Expand Up @@ -713,3 +715,104 @@ 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


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: str = pd.Field(alias="solverVersion")
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 datetime.datetime.strptime(self.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").astimezone()


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"
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 filter_by_project_name_pattern(self, naming_pattern: str):
"""Filter the projects by the naming pattern and sort by the time."""
regex = _naming_pattern_handler(pattern=naming_pattern)
self.records = [project for project in self.records if regex.match(project.name)]
self.records = sorted(
self.records, key=lambda x: x.local_time_zone_created_time, reverse=True
)
Loading

0 comments on commit 1ea562a

Please # to comment.