From 9c28447723a295f050f50a20f1955fadff0ec8c5 Mon Sep 17 00:00:00 2001
From: dreamlandliu <205407@qq.com>
Date: Fri, 6 Jan 2023 11:17:14 +0800
Subject: [PATCH] feat(client): add filter params for model/dataset/runtime
list command (#1672)
---
client/starwhale/base/bundle.py | 43 +++++-
client/starwhale/base/cloud.py | 17 ++-
client/starwhale/core/dataset/cli.py | 26 +++-
client/starwhale/core/dataset/model.py | 11 +-
client/starwhale/core/dataset/view.py | 13 +-
client/starwhale/core/model/cli.py | 26 +++-
client/starwhale/core/model/model.py | 11 +-
client/starwhale/core/model/view.py | 16 ++-
client/starwhale/core/runtime/cli.py | 26 +++-
client/starwhale/core/runtime/model.py | 11 +-
client/starwhale/core/runtime/view.py | 16 ++-
client/tests/base/test_bundle.py | 58 ++++++++
client/tests/base/test_cloud.py | 127 +++++++++++++++++-
client/tests/core/test_dataset.py | 43 ++++++
client/tests/core/test_model.py | 37 +++++
client/tests/core/test_runtime.py | 43 ++++++
.../current/reference/cli/dataset.md | 24 +++-
.../current/reference/cli/model.md | 26 ++--
.../current/reference/cli/project.md | 16 +--
.../current/reference/cli/runtime.md | 22 ++-
20 files changed, 557 insertions(+), 55 deletions(-)
create mode 100644 client/tests/base/test_bundle.py
diff --git a/client/starwhale/base/bundle.py b/client/starwhale/base/bundle.py
index 29988ef2c1..d57ca43e18 100644
--- a/client/starwhale/base/bundle.py
+++ b/client/starwhale/base/bundle.py
@@ -29,6 +29,7 @@
from starwhale.utils.config import SWCliConfigMixed
from .uri import URI
+from .store import BundleField
class BaseBundle(metaclass=ABCMeta):
@@ -76,9 +77,49 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.Union[t.Dict[str, t.Any], t.List[str]]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filters = filters or {}
_cls = cls._get_cls(project_uri)
- return _cls.list(project_uri, page, size) # type: ignore
+ _filter = cls.get_filter_dict(filters, cls.get_filter_fields())
+ return _cls.list(project_uri, page, size, _filter) # type: ignore
+
+ @classmethod
+ def get_filter_dict(
+ cls,
+ filters: t.Union[t.Dict[str, t.Any], t.List[str]],
+ fields: t.Optional[t.List[str]] = None,
+ ) -> t.Dict[str, t.Any]:
+ fields = fields or []
+ if isinstance(filters, t.Dict):
+ return {k: v for k, v in filters.items() if k in fields}
+
+ _filter_dict: t.Dict[str, t.Any] = {}
+ for _f in filters:
+ _item = _f.split("=", 1)
+ if _item[0] in fields:
+ _filter_dict[_item[0]] = _item[1] if len(_item) > 1 else ""
+ return _filter_dict
+
+ @classmethod
+ def get_filter_fields(cls) -> t.List[str]:
+ return ["name", "owner", "latest"]
+
+ @classmethod
+ def do_bundle_filter(
+ cls,
+ bundle_field: BundleField,
+ filters: t.Union[t.Dict[str, t.Any], t.List[str]],
+ ) -> bool:
+ filter_dict = cls.get_filter_dict(filters, cls.get_filter_fields())
+ _name = filter_dict.get("name")
+ if _name and not bundle_field.name.startswith(_name):
+ return False
+ _latest = filter_dict.get("latest") is not None
+ if _latest and "latest" not in bundle_field.tags:
+ return False
+
+ return True
@abstractclassmethod
def _get_cls(cls, uri: URI) -> t.Any:
diff --git a/client/starwhale/base/cloud.py b/client/starwhale/base/cloud.py
index 70be1af871..41dda5e35b 100644
--- a/client/starwhale/base/cloud.py
+++ b/client/starwhale/base/cloud.py
@@ -235,23 +235,32 @@ def _fetch_bundle_all_list(
uri_typ: str,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filter_dict: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filter_dict = filter_dict or {}
+ _params = {"pageNum": page, "pageSize": size}
+ _params.update(filter_dict)
r = self.do_http_request(
f"/project/{project_uri.project}/{uri_typ}",
- params={"pageNum": page, "pageSize": size},
+ params=_params,
instance_uri=project_uri,
).json()
-
objects = {}
+ _page = page
+ _size = size
+ if filter_dict.get("latest") is not None:
+ _page = 1
+ _size = 1
+
for o in r["data"]["list"]:
_name = f"[{o['id']}] {o['name']}"
objects[_name] = self._fetch_bundle_history(
name=o["id"],
project_uri=project_uri,
typ=uri_typ,
- page=page,
- size=size,
+ page=_page,
+ size=_size,
)[0]
return objects, self.parse_pager(r)
diff --git a/client/starwhale/core/dataset/cli.py b/client/starwhale/core/dataset/cli.py
index 3899e18f69..57e9c9beb0 100644
--- a/client/starwhale/core/dataset/cli.py
+++ b/client/starwhale/core/dataset/cli.py
@@ -118,7 +118,7 @@ def _diff(
view(base_uri).diff(URI(compare_uri, expected_type=URIType.DATASET), show_details)
-@dataset_cmd.command("list", aliases=["ls"], help="List dataset")
+@dataset_cmd.command("list", aliases=["ls"])
@click.option("-p", "--project", default="", help="Project URI")
@click.option("-f", "--fullname", is_flag=True, help="Show fullname of dataset version")
@click.option("-sr", "--show-removed", is_flag=True, help="Show removed datasets")
@@ -128,6 +128,13 @@ def _diff(
@click.option(
"--size", type=int, default=DEFAULT_PAGE_SIZE, help="Page size for dataset list"
)
+@click.option(
+ "filters",
+ "-fl",
+ "--filter",
+ multiple=True,
+ help="Filter output based on conditions provided.",
+)
@click.pass_obj
def _list(
view: DatasetTermView,
@@ -136,8 +143,23 @@ def _list(
show_removed: bool,
page: int,
size: int,
+ filters: list,
) -> None:
- view.list(project, fullname, show_removed, page, size)
+ """
+ List Dataset
+
+ The filtering flag (-fl or --filter) format is a key=value pair or a flag.
+ If there is more than one filter, then pass multiple flags.\n
+ (e.g. --filter name=mnist --filter latest)
+
+ \b
+ The currently supported filters are:
+ name\tTEXT\tThe prefix of the dataset name
+ owner\tTEXT\tThe name or id of the dataset owner
+ latest\tFLAG\t[Cloud] Only show the latest version
+ \t \t[Standalone] Only show the version with "latest" tag
+ """
+ view.list(project, fullname, show_removed, page, size, filters)
@dataset_cmd.command("info", help="Show dataset details")
diff --git a/client/starwhale/core/dataset/model.py b/client/starwhale/core/dataset/model.py
index d115cac908..e04b049b14 100644
--- a/client/starwhale/core/dataset/model.py
+++ b/client/starwhale/core/dataset/model.py
@@ -257,7 +257,9 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.Union[t.Dict[str, t.Any], t.List[str]]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filters = filters or {}
rs = defaultdict(list)
for _bf in DatasetStorage.iter_all_bundles(
@@ -265,6 +267,9 @@ def list(
bundle_type=BundleType.DATASET,
uri_type=URIType.DATASET,
):
+ if not cls.do_bundle_filter(_bf, filters):
+ continue
+
_mf = _bf.path / DEFAULT_MANIFEST_NAME
if not _mf.exists():
continue
@@ -521,9 +526,13 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filter_dict: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filter_dict = filter_dict or {}
crm = CloudRequestMixed()
- return crm._fetch_bundle_all_list(project_uri, URIType.DATASET, page, size)
+ return crm._fetch_bundle_all_list(
+ project_uri, URIType.DATASET, page, size, filter_dict
+ )
def summary(self) -> t.Optional[DatasetSummary]:
resp = self.do_http_request(
diff --git a/client/starwhale/core/dataset/view.py b/client/starwhale/core/dataset/view.py
index 334ad9624c..f982fed9c7 100644
--- a/client/starwhale/core/dataset/view.py
+++ b/client/starwhale/core/dataset/view.py
@@ -139,15 +139,16 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
-
+ filters = filters or []
if isinstance(project_uri, str):
_uri = URI(project_uri, expected_type=URIType.PROJECT)
else:
_uri = project_uri
fullname = fullname or (_uri.instance_type == InstanceType.CLOUD)
- _datasets, _pager = Dataset.list(_uri, page, size)
+ _datasets, _pager = Dataset.list(_uri, page, size, filters)
_data = BaseTermView.list_data(_datasets, show_removed, fullname)
return _data, _pager
@@ -235,9 +236,11 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
+ filters = filters or []
_datasets, _pager = super().list(
- project_uri, fullname, show_removed, page, size
+ project_uri, fullname, show_removed, page, size, filters
)
custom_column: t.Dict[str, t.Callable[[t.Any], str]] = {
"tags": lambda x: ",".join(x),
@@ -258,9 +261,11 @@ def list( # type: ignore
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> None:
+ filters = filters or []
_datasets, _pager = super().list(
- project_uri, fullname, show_removed, page, size
+ project_uri, fullname, show_removed, page, size, filters
)
cls.pretty_json(_datasets)
diff --git a/client/starwhale/core/model/cli.py b/client/starwhale/core/model/cli.py
index ce7904561a..a91e2c7c8d 100644
--- a/client/starwhale/core/model/cli.py
+++ b/client/starwhale/core/model/cli.py
@@ -120,7 +120,7 @@ def _diff(
view(base_uri).diff(URI(compare_uri, expected_type=URIType.MODEL), show_details)
-@model_cmd.command("list", aliases=["ls"], help="List Model")
+@model_cmd.command("list", aliases=["ls"])
@click.option("-p", "--project", default="", help="Project URI")
@click.option("-f", "--fullname", is_flag=True, help="Show fullname of model version")
@click.option("-sr", "--show-removed", is_flag=True, help="Show removed model")
@@ -130,6 +130,13 @@ def _diff(
@click.option(
"--size", type=int, default=DEFAULT_PAGE_SIZE, help="Page size for model list"
)
+@click.option(
+ "filters",
+ "-fl",
+ "--filter",
+ multiple=True,
+ help="Filter output based on conditions provided.",
+)
@click.pass_obj
def _list(
view: t.Type[ModelTermView],
@@ -138,8 +145,23 @@ def _list(
show_removed: bool,
page: int,
size: int,
+ filters: list,
) -> None:
- view.list(project, fullname, show_removed, page, size)
+ """
+ List Model
+
+ The filtering flag (-fl or --filter) format is a key=value pair or a flag.
+ If there is more than one filter, then pass multiple flags.\n
+ (e.g. --filter name=mnist --filter latest)
+
+ \b
+ The currently supported filters are:
+ name\tTEXT\tThe prefix of the model name
+ owner\tTEXT\tThe name or id of the model owner
+ latest\tFLAG\t[Cloud] Only show the latest version
+ \t \t[Standalone] Only show the version with "latest" tag
+ """
+ view.list(project, fullname, show_removed, page, size, filters)
@model_cmd.command("history", help="Show model history")
diff --git a/client/starwhale/core/model/model.py b/client/starwhale/core/model/model.py
index 79df774996..80cd7d8dee 100644
--- a/client/starwhale/core/model/model.py
+++ b/client/starwhale/core/model/model.py
@@ -537,13 +537,18 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.Union[t.Dict[str, t.Any], t.List[str]]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filters = filters or {}
rs = defaultdict(list)
for _bf in ModelStorage.iter_all_bundles(
project_uri,
bundle_type=BundleType.MODEL,
uri_type=URIType.MODEL,
):
+ if not cls.do_bundle_filter(_bf, filters):
+ continue
+
if _bf.path.is_file():
# for origin swmp(tar)
_manifest = ModelStorage.get_manifest_by_path(
@@ -736,9 +741,13 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filter_dict: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filter_dict = filter_dict or {}
crm = CloudRequestMixed()
- return crm._fetch_bundle_all_list(project_uri, URIType.MODEL, page, size)
+ return crm._fetch_bundle_all_list(
+ project_uri, URIType.MODEL, page, size, filter_dict
+ )
def build(self, *args: t.Any, **kwargs: t.Any) -> None:
raise NoSupportError("no support build model in the cloud instance")
diff --git a/client/starwhale/core/model/view.py b/client/starwhale/core/model/view.py
index 970526ec1d..3cf621bfe8 100644
--- a/client/starwhale/core/model/view.py
+++ b/client/starwhale/core/model/view.py
@@ -201,10 +201,12 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.Union[t.Dict[str, t.Any], t.List[str]]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
+ filters = filters or {}
_uri = URI(project_uri, expected_type=URIType.PROJECT)
fullname = fullname or (_uri.instance_type == InstanceType.CLOUD)
- _models, _pager = Model.list(_uri, page, size)
+ _models, _pager = Model.list(_uri, page, size, filters)
_data = BaseTermView.list_data(_models, show_removed, fullname)
return _data, _pager
@@ -308,8 +310,12 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
- _models, _pager = super().list(project_uri, fullname, show_removed, page, size)
+ filters = filters or []
+ _models, _pager = super().list(
+ project_uri, fullname, show_removed, page, size, filters
+ )
custom_column: t.Dict[str, t.Callable[[t.Any], str]] = {
"tags": lambda x: ",".join(x),
"size": lambda x: pretty_bytes(x),
@@ -329,8 +335,12 @@ def list( # type: ignore
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> None:
- _models, _pager = super().list(project_uri, fullname, show_removed, page, size)
+ filters = filters or []
+ _models, _pager = super().list(
+ project_uri, fullname, show_removed, page, size, filters
+ )
cls.pretty_json(_models)
def info(self, fullname: bool = False) -> None:
diff --git a/client/starwhale/core/runtime/cli.py b/client/starwhale/core/runtime/cli.py
index 778e565152..a7755d950a 100644
--- a/client/starwhale/core/runtime/cli.py
+++ b/client/starwhale/core/runtime/cli.py
@@ -265,7 +265,7 @@ def _restore(target: str) -> None:
RuntimeTermView.restore(target)
-@runtime_cmd.command("list", aliases=["ls"], help="List runtime")
+@runtime_cmd.command("list", aliases=["ls"])
@click.option("-p", "--project", default="", help="Project URI")
@click.option("-f", "--fullname", is_flag=True, help="Show fullname of runtime version")
@click.option("-sr", "--show-removed", is_flag=True, help="Show removed runtime")
@@ -275,6 +275,13 @@ def _restore(target: str) -> None:
@click.option(
"--size", type=int, default=DEFAULT_PAGE_SIZE, help="Page size for tasks list"
)
+@click.option(
+ "filters",
+ "-fl",
+ "--filter",
+ multiple=True,
+ help="Filter output based on conditions provided.",
+)
@click.pass_obj
def _list(
view: t.Type[RuntimeTermView],
@@ -283,8 +290,23 @@ def _list(
show_removed: bool,
page: int,
size: int,
+ filters: list,
) -> None:
- view.list(project, fullname, show_removed, page, size)
+ """
+ List Runtime
+
+ The filtering flag (-fl or --filter) format is a key=value pair or a flag.
+ If there is more than one filter, then pass multiple flags.\n
+ (e.g. --filter name=mnist --filter latest)
+
+ \b
+ The currently supported filters are:
+ name\tTEXT\tThe prefix of the runtime name
+ owner\tTEXT\tThe name or id of the runtime owner
+ latest\tFLAG\t[Cloud] Only show the latest version
+ \t \t[Standalone] Only show the version with "latest" tag
+ """
+ view.list(project, fullname, show_removed, page, size, filters)
@runtime_cmd.command(
diff --git a/client/starwhale/core/runtime/model.py b/client/starwhale/core/runtime/model.py
index 30bb9fa64d..9c95c49e29 100644
--- a/client/starwhale/core/runtime/model.py
+++ b/client/starwhale/core/runtime/model.py
@@ -1038,11 +1038,16 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.Union[t.Dict[str, t.Any], t.List[str]]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filters = filters or {}
rs = defaultdict(list)
for _bf in RuntimeStorage.iter_all_bundles(
project_uri, bundle_type=BundleType.RUNTIME, uri_type=URIType.RUNTIME
):
+ if not cls.do_bundle_filter(_bf, filters):
+ continue
+
if not _bf.path.is_file():
continue
@@ -1757,9 +1762,13 @@ def list(
project_uri: URI,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filter_dict: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any]]:
+ filter_dict = filter_dict or {}
crm = CloudRequestMixed()
- return crm._fetch_bundle_all_list(project_uri, URIType.RUNTIME, page, size)
+ return crm._fetch_bundle_all_list(
+ project_uri, URIType.RUNTIME, page, size, filter_dict
+ )
def build(self, *args: t.Any, **kwargs: t.Any) -> None:
raise NoSupportError("no support build runtime in the cloud instance")
diff --git a/client/starwhale/core/runtime/view.py b/client/starwhale/core/runtime/view.py
index dfebeaa879..a8349a3352 100644
--- a/client/starwhale/core/runtime/view.py
+++ b/client/starwhale/core/runtime/view.py
@@ -206,10 +206,12 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
+ filters = filters or []
_uri = URI(project_uri, expected_type=URIType.PROJECT)
fullname = fullname or (_uri.instance_type == InstanceType.CLOUD)
- _runtimes, _pager = Runtime.list(_uri, page, size)
+ _runtimes, _pager = Runtime.list(_uri, page, size, filters)
_data = BaseTermView.list_data(_runtimes, show_removed, fullname)
return _data, _pager
@@ -305,8 +307,12 @@ def list(
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> t.Tuple[t.List[t.Dict[str, t.Any]], t.Dict[str, t.Any]]:
- _data, _pager = super().list(project_uri, fullname, show_removed, page, size)
+ filters = filters or []
+ _data, _pager = super().list(
+ project_uri, fullname, show_removed, page, size, filters
+ )
custom_column: t.Dict[str, t.Callable[[t.Any], str]] = {
"tags": lambda x: ",".join(x),
@@ -327,8 +333,12 @@ def list( # type: ignore
show_removed: bool = False,
page: int = DEFAULT_PAGE_IDX,
size: int = DEFAULT_PAGE_SIZE,
+ filters: t.Optional[t.List[str]] = None,
) -> None:
- _data, _pager = super().list(project_uri, fullname, show_removed, page, size)
+ filters = filters or []
+ _data, _pager = super().list(
+ project_uri, fullname, show_removed, page, size, filters
+ )
cls.pretty_json(_data)
def info(self, fullname: bool = False) -> None:
diff --git a/client/tests/base/test_bundle.py b/client/tests/base/test_bundle.py
new file mode 100644
index 0000000000..70e2425d3c
--- /dev/null
+++ b/client/tests/base/test_bundle.py
@@ -0,0 +1,58 @@
+from pathlib import Path
+
+from pyfakefs.fake_filesystem_unittest import TestCase
+
+from starwhale.base.store import BundleField
+from starwhale.base.bundle import BaseBundle
+
+
+class TestBaseBundle(TestCase):
+ def test_get_filter_dict(self):
+ _cls = BaseBundle
+ _fields = ["name", "owner", "latest"]
+ _filter = ["name=mnist", "owner=starwhale", "latest", "other"]
+ f_dict = _cls.get_filter_dict(filters=_filter, fields=_fields)
+ print(f_dict)
+ assert f_dict.get("name") == "mnist"
+ assert f_dict.get("owner") == "starwhale"
+ assert f_dict.get("latest") is not None
+ assert not f_dict.get("other")
+
+ _filter = {"name": "nmt", "latest": True, "other": True}
+ f_dict = _cls.get_filter_dict(filters=_filter, fields=_fields)
+ assert f_dict.get("name") == "nmt"
+ assert f_dict.get("latest") is not None
+ assert not f_dict.get("owner")
+ assert not f_dict.get("other")
+
+ def test_do_bundle_filter(self):
+ _cls = BaseBundle
+ _bf = BundleField(
+ name="mnist",
+ version="5o66ol6hj3erffoc3wnyzphymgdqjqrnwwezjesk",
+ tags=["v1", "latest"],
+ path=Path(),
+ is_removed=False,
+ )
+ _filter = {"name": "nmt", "latest": True}
+ assert not _cls.do_bundle_filter(_bf, _filter)
+
+ _filter = {"name": "mnist", "latest": True}
+ assert _cls.do_bundle_filter(_bf, _filter)
+
+ _filter = {"name": "mn", "latest": True}
+ assert _cls.do_bundle_filter(_bf, _filter)
+
+ _bf = BundleField(
+ name="mnist",
+ version="5o66ol6hj3erffoc3wnyzphymgdqjqrnwwezjesk",
+ tags=["v1"],
+ path=Path(),
+ is_removed=False,
+ )
+
+ _filter = {"name": "mnist", "latest": True}
+ assert not _cls.do_bundle_filter(_bf, _filter)
+
+ _filter = {"name": "mnist"}
+ assert _cls.do_bundle_filter(_bf, _filter)
diff --git a/client/tests/base/test_cloud.py b/client/tests/base/test_cloud.py
index ccfc087a47..b983ed346f 100644
--- a/client/tests/base/test_cloud.py
+++ b/client/tests/base/test_cloud.py
@@ -1,7 +1,14 @@
+from http import HTTPStatus
+
import yaml
+from requests_mock import Mocker
from pyfakefs.fake_filesystem_unittest import TestCase
-from starwhale.base.cloud import CloudRequestMixed
+from starwhale.consts import HTTPMethod
+from starwhale.base.uri import URI
+from starwhale.base.type import URIType
+from starwhale.base.cloud import CloudRequestMixed, CloudBundleModelMixin
+from starwhale.utils.config import SWCliConfigMixed
class TestCloudRequestMixed(TestCase):
@@ -24,3 +31,121 @@ def test_get_bundle_size_from_resp(self):
item = {"meta": "no dataset byte size"}
size = ins.get_bundle_size_from_resp("dataset", item)
assert size == 0
+
+ @Mocker()
+ def test_bundle_list(self, rm: Mocker) -> None:
+ sw = SWCliConfigMixed()
+ sw.update_instance(
+ uri="http://1.1.1.1", user_name="test", sw_token="123", alias="test"
+ )
+
+ rm.request(
+ HTTPMethod.GET,
+ "http://1.1.1.1/api/v1/project/sw/model?pageNum=1&pageSize=20",
+ json={
+ "data": {
+ "list": [
+ {"id": 2, "name": "mnist"},
+ {"id": 1, "name": "text_cls"},
+ ],
+ "total": 2,
+ "size": 2,
+ }
+ },
+ status_code=HTTPStatus.OK,
+ )
+
+ rm.request(
+ HTTPMethod.GET,
+ "http://1.1.1.1/api/v1/project/sw/model?name=mnist",
+ json={
+ "data": {
+ "list": [
+ {"id": 2, "name": "mnist"},
+ ],
+ "total": 1,
+ "size": 1,
+ }
+ },
+ status_code=HTTPStatus.OK,
+ )
+
+ rm.request(
+ HTTPMethod.GET,
+ "http://1.1.1.1/api/v1/project/sw/model/1/version",
+ json={
+ "data": {
+ "list": [
+ {"id": 2, "name": "tc_v2", "createdTime": 3000},
+ {"id": 1, "name": "tc_v1", "createdTime": 1000},
+ ],
+ "total": 2,
+ "size": 2,
+ }
+ },
+ status_code=HTTPStatus.OK,
+ )
+
+ rm.request(
+ HTTPMethod.GET,
+ "http://1.1.1.1/api/v1/project/sw/model/2/version",
+ json={
+ "data": {
+ "list": [
+ {"id": 5, "name": "mnist_v3", "createdTime": 2000},
+ {"id": 4, "name": "mnist_v2", "createdTime": 2000},
+ {"id": 3, "name": "mnist_v1", "createdTime": 2000},
+ ],
+ "total": 3,
+ "size": 3,
+ }
+ },
+ status_code=HTTPStatus.OK,
+ )
+
+ rm.request(
+ HTTPMethod.GET,
+ "http://1.1.1.1/api/v1/project/sw/model/2/version?pageNum=1&pageSize=1",
+ json={
+ "data": {
+ "list": [
+ {"id": 5, "name": "mnist_v3", "createdTime": 2000},
+ ],
+ "total": 1,
+ "size": 1,
+ }
+ },
+ status_code=HTTPStatus.OK,
+ )
+
+ cbm = CloudBundleModelMixin()
+ _uri = URI("http://1.1.1.1/project/sw", expected_type=URIType.PROJECT)
+ _models, _pager = cbm._fetch_bundle_all_list(_uri, uri_typ=URIType.MODEL)
+
+ assert len(_models.items()) == 2
+ assert len(_models["[2] mnist"]) == 3
+ assert len(_models["[1] text_cls"]) == 2
+ assert _pager["current"] == 2
+ assert _pager["remain"] == 0
+
+ cbm = CloudBundleModelMixin()
+ _uri = URI("http://1.1.1.1/project/sw", expected_type=URIType.PROJECT)
+ _models, _pager = cbm._fetch_bundle_all_list(
+ _uri, uri_typ=URIType.MODEL, filter_dict={"name": "mnist"}
+ )
+
+ assert len(_models.items()) == 1
+ assert len(_models["[2] mnist"]) == 3
+ assert _pager["current"] == 1
+ assert _pager["remain"] == 0
+
+ cbm = CloudBundleModelMixin()
+ _uri = URI("http://1.1.1.1/project/sw", expected_type=URIType.PROJECT)
+ _models, _pager = cbm._fetch_bundle_all_list(
+ _uri, uri_typ=URIType.MODEL, filter_dict={"name": "mnist", "latest": True}
+ )
+
+ assert len(_models.items()) == 1
+ assert len(_models["[2] mnist"]) == 1
+ assert _pager["current"] == 1
+ assert _pager["remain"] == 0
diff --git a/client/tests/core/test_dataset.py b/client/tests/core/test_dataset.py
index 07a7426a68..c870bc8df0 100644
--- a/client/tests/core/test_dataset.py
+++ b/client/tests/core/test_dataset.py
@@ -29,6 +29,7 @@
ObjectStoreType,
)
from starwhale.utils.config import SWCliConfigMixed
+from starwhale.core.dataset.cli import _list as list_cli
from starwhale.core.dataset.cli import _build as build_cli
from starwhale.core.dataset.type import (
Line,
@@ -518,3 +519,45 @@ def test_to_list(self):
),
data_store._get_type(p),
),
+
+
+class CloudDatasetTest(TestCase):
+ def setUp(self) -> None:
+ sw_config._config = {}
+
+ def test_cli_list(self) -> None:
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ list_cli,
+ [
+ "--filter",
+ "name=mnist",
+ "--filter",
+ "owner=starwhale",
+ "--filter",
+ "latest",
+ ],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 3
+ assert "name=mnist" in call_args[5]
+ assert "owner=starwhale" in call_args[5]
+ assert "latest" in call_args[5]
+
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ list_cli,
+ [],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 0
diff --git a/client/tests/core/test_model.py b/client/tests/core/test_model.py
index c0ddf3d2e0..652e14f344 100644
--- a/client/tests/core/test_model.py
+++ b/client/tests/core/test_model.py
@@ -4,6 +4,7 @@
from pathlib import Path
from unittest.mock import patch, MagicMock
+from click.testing import CliRunner
from requests_mock import Mocker
from pyfakefs.fake_filesystem_unittest import TestCase
@@ -25,6 +26,7 @@
from starwhale.utils.config import SWCliConfigMixed
from starwhale.api._impl.job import Context, context_holder
from starwhale.core.job.model import Step
+from starwhale.core.model.cli import _list as list_cli
from starwhale.api._impl.model import PipelineHandler, PPLResultIterator
from starwhale.core.model.view import ModelTermView
from starwhale.core.model.model import StandaloneModel, resource_to_file_node
@@ -396,3 +398,38 @@ def test_serve(self, *args: t.Any):
with self.assertRaises(SystemExit):
ModelTermView.serve("set", yaml, runtime, "set", host, port)
+
+
+class CloudModelTest(TestCase):
+ def setUp(self) -> None:
+ sw_config._config = {}
+
+ def test_cli_list(self) -> None:
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ list_cli,
+ ["--filter", "name=mn", "--filter", "owner=sw", "--filter", "latest"],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 3
+ assert "name=mn" in call_args[5]
+ assert "owner=sw" in call_args[5]
+ assert "latest" in call_args[5]
+
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ list_cli,
+ [],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 0
diff --git a/client/tests/core/test_runtime.py b/client/tests/core/test_runtime.py
index 0cfb624667..eeb2912b96 100644
--- a/client/tests/core/test_runtime.py
+++ b/client/tests/core/test_runtime.py
@@ -34,6 +34,7 @@
UnExpectedConfigFieldError,
)
from starwhale.utils.config import SWCliConfigMixed
+from starwhale.core.runtime.cli import _list as runtime_list_cli
from starwhale.core.runtime.cli import _build as runtime_build_cli
from starwhale.core.runtime.view import (
get_term_view,
@@ -1978,3 +1979,45 @@ def test_create_abnormal_dependencies(self) -> None:
with self.assertRaises(FormatError):
WheelDependency(["d.d"])
+
+
+class CloudRuntimeTest(TestCase):
+ def setUp(self) -> None:
+ sw_config._config = {}
+
+ def test_cli_list(self) -> None:
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ runtime_list_cli,
+ [
+ "--filter",
+ "name=pytorch",
+ "--filter",
+ "owner=test",
+ "--filter",
+ "latest",
+ ],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 3
+ assert "name=pytorch" in call_args[5]
+ assert "owner=test" in call_args[5]
+ assert "latest" in call_args[5]
+
+ mock_obj = MagicMock()
+ runner = CliRunner()
+ result = runner.invoke(
+ runtime_list_cli,
+ [],
+ obj=mock_obj,
+ )
+
+ assert result.exit_code == 0
+ assert mock_obj.list.call_count == 1
+ call_args = mock_obj.list.call_args[0]
+ assert len(call_args[5]) == 0
diff --git a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/dataset.md b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/dataset.md
index 1da3c3f80f..29965b7ace 100644
--- a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/dataset.md
+++ b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/dataset.md
@@ -131,13 +131,20 @@ swcli dataset list [OPTIONS]
`dataset list` 命令输出当前选定的instance和project下的所有数据集及相关版本。命令参数如下:
-|参数|参数别名|必要性|类型|默认值|说明|
-|------|--------|-------|-----------|-----|-----------|
+|参数| 参数别名 |必要性|类型|默认值|说明|
+|------|-----|----|-----------|-----|-----------|
|`--project`|`-p`|❌|String|`swcli project select`命令选定的默认project|Project URI|
-|`--fullname`||❌|Boolean|False|显示完整的版本信息,默认只显示版本号的前12位。|
-|`--show-removed`||❌|Boolean|False|显示本地已经删除但能恢复的数据集。|
-|`--page`||❌|Integer|1|Cloud Instance中分页显示中page序号。|
-|`--size`||❌|Integer|20|Cloud Instance中分页显示中每页数量。|
+|`--fullname`|`-f` |❌|Boolean|False|显示完整的版本信息,默认只显示版本号的前12位。|
+|`--show-removed`|`-sr`| ❌ |Boolean|False|显示本地已经删除但能恢复的数据集。|
+|`--page`| | ❌ |Integer|1|Cloud Instance中分页显示中page序号。|
+|`--size`| | ❌ |Integer|20|Cloud Instance中分页显示中每页数量。|
+| `--filter` |`-fl`| ❌ | String | | 过滤器,使用key=value格式或者flag,可使用多个filter,具体支持的filter如下: |
+
+| Filter | 类型 | 说明 | 示例|
+|----|-----------|-------------------------------------------------------|----|
+|`name`| Key-Value | 数据集名称前缀 |--filter name=mnist|
+|`owner`| Key-Value | 拥有者名称 |--filter owner=starwhale|
+|`latest`|Flag| Cloud Instance: 仅展示最新版本
Standalone Instance: 仅展示带有latest标签的版本 |--filter latest|
`dataset list` 的alias命令为 `dataset ls`。
@@ -147,7 +154,10 @@ swcli dataset list [OPTIONS]
swcli dataset remove [OPTIONS] DATASET
```
-`dataset remove` 命令可以删除整个数据集或数据集中的某个版本。删除可以分为硬删除和软删除,默认为软删除,指定 `--force` 参数为硬删除。所有的软删除数据集在没有GC之前,都可以通过 `swcli dataset recover` 命令进行恢复。`DATASET` 参数为Dataset URI,当没有指定版本时,会对整个数据集所有版本进行删除,若URI带有版本信息,则只会对该版本进行删除。软删除的数据集,可以通过 `swcli dataset list --show-removed` 命令查看。
+`dataset remove` 命令可以删除整个数据集或数据集中的某个版本。删除可以分为硬删除和软删除,默认为软删除,指定 `--force`
+参数为硬删除。所有的软删除数据集在没有GC之前,都可以通过 `swcli dataset recover` 命令进行恢复。`DATASET` 参数为Dataset
+URI,当没有指定版本时,会对整个数据集所有版本进行删除,若URI带有版本信息,则只会对该版本进行删除。软删除的数据集,可以通过 `swcli dataset list --show-removed`
+命令查看。
|参数|参数别名|必要性|类型|默认值|说明|
|------|--------|-------|-----------|-----|-----------|
diff --git a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/model.md b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/model.md
index 76503ea7a7..0156aaea99 100644
--- a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/model.md
+++ b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/model.md
@@ -117,13 +117,20 @@ swcli model list [OPTIONS]
`model list` 命令输出当前选定的instance和project下的所有模型包及相关版本。命令参数如下:
-|参数|参数别名|必要性|类型|默认值|说明|
-|------|--------|-------|-----------|-----|-----------|
-|`--project`|`-p`|❌|String|`swcli project select`命令选定的默认project|Project URI|
-|`--fullname`||❌|Boolean|False|显示完整的版本信息,默认只显示版本号的前12位。|
-|`--show-removed`||❌|Boolean|False|显示本地已经删除但能恢复的模型包。|
-|`--page`||❌|Integer|1|Cloud Instance中分页显示中page序号。|
-|`--size`||❌|Integer|20|Cloud Instance中分页显示中每页数量。|
+|参数|参数别名|必要性|类型|默认值| 说明 |
+|---|---|---|---|---|----------------------------------------------------|
+|`--project`|`-p`|❌|String|`swcli project select`命令选定的默认project | Project URI|
+|`--fullname`|`-f`|❌|Boolean|False| 显示完整的版本信息,默认只显示版本号的前12位。|
+|`--show-removed`|`-sr`|❌|Boolean|False| 显示本地已经删除但能恢复的模型包。|
+|`--page`| |❌|Integer|1| Cloud Instance中分页显示中page序号。|
+|`--size`| |❌|Integer|20| Cloud Instance中分页显示中每页数量。|
+|`--filter`|`-fl`|❌|String| | 过滤器,使用key=value格式或者flag,可使用多个filter,具体支持的filter如下: |
+
+|Filter|类型| 说明 | 示例|
+|----|-------|-----------|----|
+|`name`| Key-Value | 模型名称前缀 |--filter name=mnist|
+|`owner`| Key-Value | 拥有者名称 |--filter owner=starwhale|
+|`latest`|Flag| Cloud Instance: 仅展示最新版本
Standalone Instance: 仅展示带有latest标签的版本 |--filter latest|
## 8. 删除模型包
@@ -131,7 +138,10 @@ swcli model list [OPTIONS]
swcli model remove [OPTIONS] MODEL
```
-`model remove` 命令可以删除整个模型包或模型包中的某个版本。删除可以分为硬删除和软删除,默认为软删除,指定 `--force` 参数为硬删除。所有的软删除模型包在没有GC之前,都可以通过 `swcli model recover` 命令进行恢复。`MODEL` 参数为Model URI,当没有指定版本时,会对整个模型包所有版本进行删除,若URI带有版本信息,则只会对该版本进行删除。软删除的模型包,可以通过 `swcli model list --show-removed` 命令查看。
+`model remove` 命令可以删除整个模型包或模型包中的某个版本。删除可以分为硬删除和软删除,默认为软删除,指定 `--force`
+参数为硬删除。所有的软删除模型包在没有GC之前,都可以通过 `swcli model recover` 命令进行恢复。`MODEL` 参数为Model
+URI,当没有指定版本时,会对整个模型包所有版本进行删除,若URI带有版本信息,则只会对该版本进行删除。软删除的模型包,可以通过 `swcli model list --show-removed`
+命令查看。
|参数|参数别名|必要性|类型|默认值|说明|
|------|--------|-------|-----------|-----|-----------|
diff --git a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/project.md b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/project.md
index bd406ef182..17e9c1b62c 100644
--- a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/project.md
+++ b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/project.md
@@ -14,14 +14,14 @@ project命令提供适用于Standalone Instance和Cloud Instance的Starwhale Pro
project包含如下子命令:
-|命令|别名|Standalone|Cloud|
-|-------|----------|-----|
-|create|new,add|✅|✅|
-|info||✅|✅|
-|list|ls|✅|✅|
-|remove|rm|✅|✅|
-|recover||✅|✅|
-|select|use|✅|✅|
+|命令| 别名 |Standalone|Cloud|
+|----|---------|----------|-----|
+|create| new,add |✅|✅|
+|info|| ✅ |✅|
+|list| ls |✅|✅|
+|remove| rm |✅|✅|
+|recover|| ✅ |✅|
+|select| use |✅|✅|
## 2. 创建Project
diff --git a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/runtime.md b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/runtime.md
index 52cc251ef0..3f8954d516 100644
--- a/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/runtime.md
+++ b/docs/i18n/zh/docusaurus-plugin-content-docs/current/reference/cli/runtime.md
@@ -174,13 +174,20 @@ swcli runtime list [OPTIONS]
`runtime list` 命令输出当前选定的instance和project下的所有Runtime及相关版本。命令参数如下:
-|参数|参数别名|必要性|类型|默认值|说明|
-|------|--------|-------|-----------|-----|-----------|
+|参数| 参数别名 |必要性|类型|默认值|说明|
+|------|------|-------|-----------|-----|-----------|
|`--project`|`-p`|❌|String|`swcli project select`命令选定的默认project|Project URI|
-|`--fullname`||❌|Boolean|False|显示完整的版本信息,默认只显示版本号的前12位。|
-|`--show-removed`||❌|Boolean|False|显示本地已经删除但能恢复的Runtime。|
-|`--page`||❌|Integer|1|Cloud Instance中分页显示中page序号。|
-|`--size`||❌|Integer|20|Cloud Instance中分页显示中每页数量。|
+|`--fullname`|`-f`|❌|Boolean|False|显示完整的版本信息,默认只显示版本号的前12位。|
+|`--show-removed`|`-sr`|❌|Boolean|False|显示本地已经删除但能恢复的Runtime。|
+|`--page`| |❌|Integer|1|Cloud Instance中分页显示中page序号。|
+|`--size`| |❌|Integer|20|Cloud Instance中分页显示中每页数量。|
+|`--filter`|`-fl`|❌| String | | 过滤器,使用key=value格式或者flag,可使用多个filter,具体支持的filter如下: |
+
+|Filter|类型|说明|示例|
+|----|--------|----------|----------|
+|`name`|Key-Value|Runtime名称前缀|--filter name=pytorch|
+|`owner`| Key-Value|拥有者名称|--filter owner=starwhale|
+|`latest`|Flag|Cloud Instance: 仅展示最新版本
Standalone Instance: 仅展示带有latest标签的版本|--filter latest|
## 10. 锁定Python依赖信息
@@ -188,7 +195,8 @@ swcli runtime list [OPTIONS]
swcli runtime lock [OPTIONS] [TARGET_DIR]
```
-`runtime lock` 命令能将指定的Python环境中的依赖进行导出,在 `TARGET_DIR` 目录中生成 `requirements-sw-lock.txt` 文件或 `conda-sw-lock.yaml` 文件,典型内容形式如下:
+`runtime lock` 命令能将指定的Python环境中的依赖进行导出,在 `TARGET_DIR` 目录中生成 `requirements-sw-lock.txt`
+文件或 `conda-sw-lock.yaml` 文件,典型内容形式如下:
```txt
# Generated by Starwhale(0.3.0) Runtime Lock