From a96efe0cfe9655fbdbbe282fd0d6a2feb09e622f Mon Sep 17 00:00:00 2001
From: Asymptotez <201220101@smail.nju.edu.cn>
Date: Tue, 28 Nov 2023 10:21:50 +0800
Subject: [PATCH 01/24] [DOC] rewrite the client.rst document
---
docs/workflows/client.rst | 192 +++++++++++++++++++++-----------------
1 file changed, 107 insertions(+), 85 deletions(-)
diff --git a/docs/workflows/client.rst b/docs/workflows/client.rst
index 5f0ea5b..cfdfb02 100644
--- a/docs/workflows/client.rst
+++ b/docs/workflows/client.rst
@@ -6,13 +6,13 @@ Learnware Client
Introduction
====================
-``Learnware Client`` is a python api that provides a convenient interface for interacting with the official market. You can easily use the client to upload, download and search learnwares.
+``Learnware Client`` is a python api that provides a convenient interface for interacting with the system. You can easily use the client to upload, download and search learnwares.
Prepare access token
====================
-Before using the ``Learnware Client``, you'll need to obtain a token from the `official website `_. Just login to the website and click "client token" tab in the user center.
+Before using the ``Learnware Client``, you'll need to obtain a token from the `official website `_. Just login to the website and click ``Client Token`` tab in the user center.
How to Use Client
@@ -25,142 +25,164 @@ Initialize a Learware Client
.. code-block:: python
- import learnware
- from learnware.client import LearnwareClient
+ from learnware.client import LearnwareClient, SemanticSpecificationKey
+ # Login to Beiming system
client = LearnwareClient()
-
- # login to official market
client.login(email="your email", token="your token")
+Where email is the registered mailbox of the system and token is the token obtained in the previous section.
Upload Leanware
-------------------------------
-Before uploading a learnware, you'll need to prepare the semantic specification of your learnware. You can create a semantic specification by a helper function ``create_semantic_specification``.
+Before uploading a learnware, you'll need to prepare the semantic specification of your learnware. Let's take the classification task for tabular data as an example. You can create a semantic specification by a helper function ``create_semantic_specification``.
.. code-block:: python
+ # Prepare input description when data_type="Table"
input_description = {
- "Dimension": 16,
- {
- "Description": {
- "0": "gender",
- "1": "age",
- "2": "f2",
- "5": "f5"
- }
- }
+ "Dimension": 5,
+ "Description": {
+ "0": "age",
+ "1": "weight",
+ "2": "body length",
+ "3": "animal type",
+ "4": "claw length"
+ },
}
+
+ # Prepare output description when task_type in ["Classification", "Regression"]
output_description = {
"Dimension": 3,
"Description": {
- "0": "the probability of being a cat",
- "1": "the probability of being a dog",
- "2": "the probability of being a bird"
- }
+ "0": "cat",
+ "1": "dog",
+ "2": "bird",
+ },
}
+
+ # Create semantic specification
semantic_spec = client.create_semantic_specification(
- name="mylearnware1",
- description="this is my learnware",
- data_type="Table",
- task_type="Classification",
- library_type="Scikit-learn",
- senarioes=["Business", "Financial"],
- input_description, output_description)
- # data_type, task_type, library_type, senarioes are enums, you can find possible values in `learnware.C`
+ name="learnware_example",
+ description="Just a example for uploading a learnware",
+ data_type="Table",
+ task_type="Classification",
+ library_type="Scikit-learn",
+ scenarios=["Business", "Financial"],
+ input_description=input_description,
+ output_description=output_description,
+ )
+Make sure that the parameter input for the semantic specification is within the range given by ``client.list_semantic_specification_values(key)`` :
+
+* data_type must in ``key=SemanticSpecificationKey.DATA_TYPE``;
+* task_type must in ``key=SemanticSpecificationKey.TASK_TYPE``;
+* library_type must in ``key=SemanticSpecificationKey.LIBRARY_TYPE``;
+* scenarios must be a subset of ``key=SemanticSpecificationKey.SENARIOES``;
+* When data_type is ``"Table"``, input description needs to be filled in;
+* When task_type is in ``["Classification", "Regression"]``, output description needs to be filled.
+
+Finally, the semantic specification and the zip package path of the learnware were filled in to upload the learnware.
+
+Remember to verify the learnware before uploading it, as shown in the following code example:
-After defining the semantic specification,
-you can upload your learnware using ``upload_learnware`` function:
-
.. code-block:: python
-
+
+ # Prepare your learnware zip file
+ zip_path = "your learnware zip"
+
+ # Check your learnware before upload
+ client.check_learnware(
+ learnware_zip_path=zip_path, semantic_specification=semantic_spec
+ )
+
+ # Upload your learnware
learnware_id = client.upload_learnware(
- semantic_spec=semantic_spec,
- zip_path="path to your learnware zipfile")
+ learnware_zip_path=zip_path, semantic_specification=semantic_spec
+ )
-Here, ``zip_path`` is the local path of your learnware zipfile.
+After uploading the learnware successfully, you can see it in ``My Learnware``, the background will check it. Click on the learnware, which can be viewed in the ``Verify Status``. After the check passes, the Unverified tag of the learnware will disappear, and the uploaded learnware will appear in the system.
Semantic Specification Search
-------------------------------
-You can search learnwares in official market using semantic specification. All the learnwares that match the semantic specification will be returned by the api. For example, the code below searches learnwares with `Table` data type:
+You can search the learnware in the system through the semantic specification, and all the learnware conforming to the semantic specification will be returned through the API. For example, the following code will give you all the learnware in the system whose task type is classified:
.. code-block:: python
- semantic_spec = client.create_semantic_specification(
- name="",
- description="",
- data_type="Table",
- task_type="",
- library_type="",
- senarioes=[],
- input_description={}, output_description={})
-
- specification = learnware.specification.Specification()
- specification.update_semantic_spec(specification)
- learnware_list = client.search_learnware(specification)
+ from learnware.market import BaseUserInfo
+
+ user_semantic = client.create_semantic_specification(
+ task_type="Classification"
+ )
+ user_info = BaseUserInfo(semantic_spec=user_semantic)
+ learnware_list = client.search_learnware(user_info, page_size=None)
Statistical Specification Search
---------------------------------
-You can search learnware by providing a statistical specification. The statistical specification is a json file that contains the statistical information of your training data. For example, the code below searches learnwares with `RKMETableSpecification`:
+You can also search the learnware in the system through the statistical specification, and all the learnware with similar distribution will be returned through the API. Using the ``generate_stat_spec`` function mentioned above, you can easily get the ``stat_spec`` for your current task, and then get the learnware that meets the statistical specification for the same type of data in the system by using the following code:
.. code-block:: python
- import learnware.specification as specification
-
- user_spec = specification.RKMETableSpecification()
- user_spec.load(os.path.join(unzip_path, "rkme.json"))
-
- specification = learnware.specification.Specification()
- specification.update_stat_spec(user_spec)
-
- learnware_list = client.search_learnware(specification)
-
- # you can view the scores of the searched learnwares
- for learnware in learnware_list:
- print(f'learnware_id: {learnware["learnware_id"]}, score: {learnware["matching"]}')
+ user_info = BaseUserInfo(stat_info={stat_spec.type: stat_spec})
+ learnware_list = client.search_learnware(user_info, page_size=None)
Combine Semantic and Statistical Search
----------------------------------------
-You can provide both semantic and statistical specification to search learnwares. The engine will first filter learnwares by semantic specification and then search by statistical specification. For example, the code below searches learnwares with `Table` data type and `RKMETableSpecification`:
+By combining statistical and semantic specifications, you can perform more detailed searches, such as the following code that searches tabular data for pieces of learnware that satisfy your semantic specifications:
.. code-block:: python
- semantic_spec = client.create_semantic_specification(
- name="",
- description="",
- data_type="Table",
- task_type="",
- library_type="",
- senarioes=[],
- input_description={}, output_description={})
+ user_semantic = client.create_semantic_specification(
+ task_type="Classification",
+ scenarios=["Business"],
+ )
+ rkme_table = generate_stat_spec(type="table", X=train_x)
+ user_info = BaseUserInfo(
+ semantic_spec=user_semantic, stat_info={rkme_table.type: rkme_table}
+ )
+ learnware_list = client.search_learnware(user_info, page_size=None)
+
+Heterogeneous Table Search
+----------------------------------------
+When you provide a statistical specification for tabular data, the task type is "Classification" or "Regression", and your semantic specification includes descriptions for each dimension, the system will automatically enable heterogeneous table search. It won't only search in the tabular learnwares with same dimensions. The following code will perform heterogeneous table search through the API:
- stat_spec = specification.RKMETableSpecification()
- stat_spec.load(os.path.join(unzip_path, "rkme.json"))
- specification = learnware.specification.Specification()
- specification.update_semantic_spec(semantic_spec)
- specification.update_stat_spec(stat_spec)
+.. code-block:: python
- learnware_list = client.search_learnware(specification)
+ input_description = {
+ "Dimension": 2,
+ "Description": {
+ "0": "leaf width",
+ "1": "leaf length",
+ },
+ }
+ user_semantic = client.create_semantic_specification(
+ task_type="Classification",
+ scenarios=["Business"],
+ input_description=input_description,
+ )
+ rkme_table = generate_stat_spec(type="table", X=train_x)
+ user_info = BaseUserInfo(
+ semantic_spec=user_semantic, stat_info={rkme_table.type: rkme_table}
+ )
+ learnware_list = client.search_learnware(user_info)
Download and Use Learnware
-------------------------------
-When you get a learnware id, you can download and initiate the learnware with the following code:
+When the search is complete, you can download the learnware and configure the environment through the following code:
.. code-block:: python
- client.download_learnware(learnware_id, zip_path)
- client.install_environment(zip_path)
- learnware = client.load_learnware(zip_path)
- # you can use the learnware to make prediction now
-
-
-
+ for temp_learnware in learnware_list:
+ learnware_id = temp_learnware["learnware_id"]
+ # you can use the learnware to make prediction now
+ learnware = client.load_learnware(
+ learnware_id=learnware_id, runnable_option="conda"
+ )
\ No newline at end of file
From 061760f8fbe4b791ad151a4e7bbe30dcdece7f1d Mon Sep 17 00:00:00 2001
From: bxdd <45119470+bxdd@users.noreply.github.com>
Date: Tue, 28 Nov 2023 16:30:00 +0800
Subject: [PATCH 02/24] Update TypeHint in create_semantic_spec
---
learnware/client/learnware_client.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/learnware/client/learnware_client.py b/learnware/client/learnware_client.py
index 5b24602..4491863 100644
--- a/learnware/client/learnware_client.py
+++ b/learnware/client/learnware_client.py
@@ -281,15 +281,15 @@ class LearnwareClient:
def create_semantic_specification(
self,
- name: str = None,
- description: str = None,
- data_type: str = None,
- task_type: str = None,
- library_type: str = None,
- scenarios: Union[str, List(str)] = None,
- license: Union[str, List(str)] = None,
- input_description: dict = None,
- output_description: dict = None,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ data_type: Optional[str] = None,
+ task_type: Optional[str] = None,
+ library_type: Optional[str] = None,
+ scenarios: Optional[Union[str, List(str)]] = None,
+ license: Optional[Union[str, List(str)]] = None,
+ input_description: Optional[dict] = None,
+ output_description: Optional[dict] = None,
):
semantic_specification = dict()
semantic_specification["Data"] = {"Type": "Class", "Values": [data_type] if data_type is not None else []}
From 18640f9f0ea51a97876cde10f08b930e2febd927 Mon Sep 17 00:00:00 2001
From: Gene
Date: Tue, 28 Nov 2023 16:31:56 +0800
Subject: [PATCH 03/24] [FIX] fix details
---
learnware/client/learnware_client.py | 4 ++--
tests/test_workflow/test_workflow.py | 17 +++++++++++------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/learnware/client/learnware_client.py b/learnware/client/learnware_client.py
index 5b24602..beb7bf9 100644
--- a/learnware/client/learnware_client.py
+++ b/learnware/client/learnware_client.py
@@ -286,8 +286,8 @@ class LearnwareClient:
data_type: str = None,
task_type: str = None,
library_type: str = None,
- scenarios: Union[str, List(str)] = None,
- license: Union[str, List(str)] = None,
+ scenarios: Union[str, List[str]] = None,
+ license: Union[str, List[str]] = None,
input_description: dict = None,
output_description: dict = None,
):
diff --git a/tests/test_workflow/test_workflow.py b/tests/test_workflow/test_workflow.py
index 0702a13..83f39ba 100644
--- a/tests/test_workflow/test_workflow.py
+++ b/tests/test_workflow/test_workflow.py
@@ -27,6 +27,7 @@ user_semantic = {
"Scenario": {"Values": ["Education"], "Type": "Tag"},
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "", "Type": "String"},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -145,11 +146,15 @@ class TestWorkflow(unittest.TestCase):
user_info = BaseUserInfo(semantic_spec=semantic_spec)
search_result = easy_market.search_learnware(user_info)
single_result = search_result.get_single_results()
-
+
print("User info:", user_info.get_semantic_spec())
print(f"Search result:")
for search_item in single_result:
- print("Choose learnware:", search_item.learnware.id, search_item.learnware.get_specification().get_semantic_spec())
+ print(
+ "Choose learnware:",
+ search_item.learnware.id,
+ search_item.learnware.get_specification().get_semantic_spec(),
+ )
rmtree(test_folder) # rm -r test_folder
@@ -176,12 +181,12 @@ class TestWorkflow(unittest.TestCase):
single_result = search_results.get_single_results()
multiple_result = search_results.get_multiple_results()
-
+
assert len(single_result) >= 1, f"Statistical search failed!"
print(f"search result of user{idx}:")
for search_item in single_result:
print(f"score: {search_item.score}, learnware_id: {search_item.learnware.id}")
-
+
for mixture_item in multiple_result:
print(f"mixture_score: {mixture_item.score}\n")
mixture_id = " ".join([learnware.id for learnware in mixture_item.learnwares])
@@ -229,8 +234,8 @@ class TestWorkflow(unittest.TestCase):
def suite():
_suite = unittest.TestSuite()
- #_suite.addTest(TestWorkflow("test_prepare_learnware_randomly"))
- #_suite.addTest(TestWorkflow("test_upload_delete_learnware"))
+ # _suite.addTest(TestWorkflow("test_prepare_learnware_randomly"))
+ # _suite.addTest(TestWorkflow("test_upload_delete_learnware"))
_suite.addTest(TestWorkflow("test_search_semantics"))
_suite.addTest(TestWorkflow("test_stat_search"))
_suite.addTest(TestWorkflow("test_learnware_reuse"))
From c35505aa5ae7cdacffb65fbd97c23013e932c4e9 Mon Sep 17 00:00:00 2001
From: bxdd <45119470+bxdd@users.noreply.github.com>
Date: Tue, 28 Nov 2023 16:35:50 +0800
Subject: [PATCH 04/24] [FIX] fix typehint in Client
---
learnware/client/learnware_client.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/learnware/client/learnware_client.py b/learnware/client/learnware_client.py
index 4491863..bb5e9a9 100644
--- a/learnware/client/learnware_client.py
+++ b/learnware/client/learnware_client.py
@@ -286,8 +286,8 @@ class LearnwareClient:
data_type: Optional[str] = None,
task_type: Optional[str] = None,
library_type: Optional[str] = None,
- scenarios: Optional[Union[str, List(str)]] = None,
- license: Optional[Union[str, List(str)]] = None,
+ scenarios: Optional[Union[str, List[str]]] = None,
+ license: Optional[Union[str, List[str]]] = None,
input_description: Optional[dict] = None,
output_description: Optional[dict] = None,
):
From 27a23009e6d92f1f3e7c41c87cc69ae727f57bfb Mon Sep 17 00:00:00 2001
From: Gene
Date: Tue, 28 Nov 2023 16:38:58 +0800
Subject: [PATCH 05/24] [FIX] add license in semantic spec
---
examples/dataset_image_workflow/main.py | 7 ++++++-
examples/dataset_m5_workflow/main.py | 4 +++-
examples/dataset_m5_workflow/upload.py | 1 +
examples/dataset_pfs_workflow/main.py | 4 +++-
examples/dataset_pfs_workflow/upload.py | 1 +
examples/dataset_text_workflow/main.py | 16 +++++++---------
tests/test_hetero_market/test_hetero.py | 15 +++++++++------
7 files changed, 30 insertions(+), 18 deletions(-)
diff --git a/examples/dataset_image_workflow/main.py b/examples/dataset_image_workflow/main.py
index c91981c..0512c31 100644
--- a/examples/dataset_image_workflow/main.py
+++ b/examples/dataset_image_workflow/main.py
@@ -50,6 +50,7 @@ semantic_specs = [
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "learnware_1", "Type": "String"},
"Output": {"Dimension": 10},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
]
@@ -60,6 +61,7 @@ user_semantic = {
"Scenario": {"Values": ["Business"], "Type": "Tag"},
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "", "Type": "String"},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -175,7 +177,10 @@ def test_search(gamma=0.1, load_market=True):
pred_y = single_item.learnware.predict(user_data)
acc = eval_prediction(pred_y, user_label)
acc_list.append(acc)
- logger.info("Search rank: %d, score: %.3f, learnware_id: %s, acc: %.3f" % (idx, single_item.score, single_item.learnware.id, acc))
+ logger.info(
+ "Search rank: %d, score: %.3f, learnware_id: %s, acc: %.3f"
+ % (idx, single_item.score, single_item.learnware.id, acc)
+ )
# test reuse (job selector)
# reuse_baseline = JobSelectorReuser(learnware_list=mixture_learnware_list, herding_num=100)
diff --git a/examples/dataset_m5_workflow/main.py b/examples/dataset_m5_workflow/main.py
index 60ee439..c6dea40 100644
--- a/examples/dataset_m5_workflow/main.py
+++ b/examples/dataset_m5_workflow/main.py
@@ -36,6 +36,7 @@ semantic_specs = [
"Name": {"Values": "learnware_1", "Type": "String"},
"Input": input_description,
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
]
@@ -48,6 +49,7 @@ user_semantic = {
"Name": {"Values": "", "Type": "String"},
"Input": input_description,
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -158,7 +160,7 @@ class M5DatasetWorkflow:
search_result = easy_market.search_learnware(user_info)
single_result = search_result.get_single_results()
multiple_result = search_result.get_multiple_results()
-
+
print(f"search result of user{idx}:")
print(
f"single model num: {len(single_result)}, max_score: {single_result[0].score}, min_score: {single_result[-1].score}"
diff --git a/examples/dataset_m5_workflow/upload.py b/examples/dataset_m5_workflow/upload.py
index 0c9e209..af0a69d 100644
--- a/examples/dataset_m5_workflow/upload.py
+++ b/examples/dataset_m5_workflow/upload.py
@@ -66,6 +66,7 @@ def main():
"Scenario": {"Values": ["Business"], "Type": "Tag"},
"Description": {"Values": "A sales-forecasting model from Walmart store", "Type": "String"},
"Name": {"Values": name, "Type": "String"},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
res = session.post(
submit_url,
diff --git a/examples/dataset_pfs_workflow/main.py b/examples/dataset_pfs_workflow/main.py
index 74c4da5..2c33f04 100644
--- a/examples/dataset_pfs_workflow/main.py
+++ b/examples/dataset_pfs_workflow/main.py
@@ -35,6 +35,7 @@ semantic_specs = [
"Name": {"Values": "learnware_1", "Type": "String"},
"Input": input_description,
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
]
@@ -47,6 +48,7 @@ user_semantic = {
"Name": {"Values": "", "Type": "String"},
"Input": input_description,
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -155,7 +157,7 @@ class PFSDatasetWorkflow:
search_result = easy_market.search_learnware(user_info)
single_result = search_result.get_single_results()
multiple_result = search_result.get_multiple_results()
-
+
print(f"search result of user{idx}:")
print(
f"single model num: {len(single_result)}, max_score: {single_result[0].score}, min_score: {single_result[-1].score}"
diff --git a/examples/dataset_pfs_workflow/upload.py b/examples/dataset_pfs_workflow/upload.py
index 9719230..c9da3db 100644
--- a/examples/dataset_pfs_workflow/upload.py
+++ b/examples/dataset_pfs_workflow/upload.py
@@ -69,6 +69,7 @@ def main():
"Type": "String",
},
"Name": {"Values": name, "Type": "String"},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
res = session.post(
submit_url,
diff --git a/examples/dataset_text_workflow/main.py b/examples/dataset_text_workflow/main.py
index c5715e7..dcb3898 100644
--- a/examples/dataset_text_workflow/main.py
+++ b/examples/dataset_text_workflow/main.py
@@ -48,6 +48,7 @@ semantic_specs = [
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "learnware_1", "Type": "String"},
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
]
@@ -59,6 +60,7 @@ user_semantic = {
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "", "Type": "String"},
"Output": output_description,
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -73,7 +75,6 @@ class TextDatasetWorkflow:
generate_uploader(X_train, y_train, n_uploaders=n_uploaders, data_save_root=uploader_save_root)
generate_user(X_test, y_test, n_users=n_users, data_save_root=user_save_root)
-
def _prepare_model(self):
dataloader = TextDataLoader(data_save_root, train=True)
for i in range(n_uploaders):
@@ -92,9 +93,8 @@ class TextDatasetWorkflow:
logger.info("Model saved to '%s' and '%s'" % (modelv_save_path, modell_save_path))
-
- def _prepare_learnware(self,
- data_path, modelv_path, modell_path, init_file_path, yaml_path, env_file_path, save_root, zip_name
+ def _prepare_learnware(
+ self, data_path, modelv_path, modell_path, init_file_path, yaml_path, env_file_path, save_root, zip_name
):
os.makedirs(save_root, exist_ok=True)
tmp_spec_path = os.path.join(save_root, "rkme.json")
@@ -139,7 +139,6 @@ class TextDatasetWorkflow:
logger.info("New Learnware Saved to %s" % (zip_file_name))
return zip_file_name
-
def prepare_market(self, regenerate_flag=False):
if regenerate_flag:
self._init_text_dataset()
@@ -175,7 +174,6 @@ class TextDatasetWorkflow:
logger.info("Total Item: %d" % (len(text_market)))
-
def test(self, regenerate_flag=False):
self.prepare_market(regenerate_flag)
text_market = instantiate_learnware_market(market_id="ae")
@@ -199,11 +197,11 @@ class TextDatasetWorkflow:
user_stat_spec.generate_stat_spec_from_data(X=user_data)
user_info = BaseUserInfo(semantic_spec=user_semantic, stat_info={"RKMETextSpecification": user_stat_spec})
logger.info("Searching Market for user: %d" % (i))
-
+
search_result = text_market.search_learnware(user_info)
single_result = search_result.get_single_results()
multiple_result = search_result.get_multiple_results()
-
+
print(f"search result of user{i}:")
print(
f"single model num: {len(single_result)}, max_score: {single_result[0].score}, min_score: {single_result[-1].score}"
@@ -220,7 +218,7 @@ class TextDatasetWorkflow:
print(
f"Top1-score: {single_result[0].score}, learnware_id: {single_result[0].learnware.id}, acc: {acc_list[0]}"
)
-
+
if len(multiple_result) > 0:
mixture_id = " ".join([learnware.id for learnware in multiple_result[0].learnwares])
print(f"mixture_score: {multiple_result[0].score}, mixture_learnware: {mixture_id}")
diff --git a/tests/test_hetero_market/test_hetero.py b/tests/test_hetero_market/test_hetero.py
index be828e5..41b4261 100644
--- a/tests/test_hetero_market/test_hetero.py
+++ b/tests/test_hetero_market/test_hetero.py
@@ -35,6 +35,7 @@ user_semantic = {
"Scenario": {"Values": ["Education"], "Type": "Tag"},
"Description": {"Values": "", "Type": "String"},
"Name": {"Values": "", "Type": "String"},
+ "License": {"Values": ["MIT"], "Type": "Class"},
}
@@ -259,15 +260,15 @@ class TestMarket(unittest.TestCase):
str(key): semantic_spec["Input"]["Description"][str(key)] for key in range(user_dim)
}
user_info = BaseUserInfo(semantic_spec=semantic_spec, stat_info={"RKMETableSpecification": user_spec})
-
+
search_result = hetero_market.search_learnware(user_info)
single_result = search_result.get_single_results()
multiple_result = search_result.get_multiple_results()
-
+
print(f"search result of user{idx}:")
for single_item in single_result:
print(f"score: {single_item.score}, learnware_id: {single_item.learnware.id}")
-
+
for multiple_item in multiple_result:
print(
f"mixture_score: {multiple_item.score}, mixture_learnware_ids: {[item.id for item in multiple_item.learnwares]}"
@@ -335,7 +336,7 @@ class TestMarket(unittest.TestCase):
print(f"search result of user{idx}:")
for single_item in single_result:
print(f"score: {single_item.score}, learnware_id: {single_item.learnware.id}")
-
+
for multiple_item in multiple_result:
print(f"mixture_score: {multiple_item.score}\n")
mixture_id = " ".join([learnware.id for learnware in multiple_item.learnwares])
@@ -363,9 +364,11 @@ class TestMarket(unittest.TestCase):
# print search results
for single_item in single_result:
print(f"score: {single_item.score}, learnware_id: {single_item.learnware.id}")
-
+
for multiple_item in multiple_result:
- print(f"mixture_score: {multiple_item.score}, mixture_learnware_ids: {[item.id for item in multiple_item.learnwares]}")
+ print(
+ f"mixture_score: {multiple_item.score}, mixture_learnware_ids: {[item.id for item in multiple_item.learnwares]}"
+ )
# single model reuse
hetero_learnware = HeteroMapAlignLearnware(single_result[0].learnware, mode="regression")
From bb6c2854bc3a7ba658b3fd287763468d10950b32 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Tue, 28 Nov 2023 22:49:54 +0800
Subject: [PATCH 06/24] [DOC] update doc style
---
docs/_static/css/custom_style.css | 21 ++-------------------
docs/conf.py | 8 ++++----
setup.py | 2 +-
3 files changed, 7 insertions(+), 24 deletions(-)
diff --git a/docs/_static/css/custom_style.css b/docs/_static/css/custom_style.css
index 2475bcd..6c2d87e 100644
--- a/docs/_static/css/custom_style.css
+++ b/docs/_static/css/custom_style.css
@@ -1,20 +1,3 @@
-.bd-main {
- flex-grow: 1;
- flex-direction: column;
- display: flex;
- min-width: 0;
-}
-
-.bd-main .bd-content {
- justify-content: left;
-}
-
-.bd-main .bd-content .bd-article-container {
- max-width: calc(100% - var(--pst-sidebar-secondary));
-}
-
-.bd-sidebar-primary div#rtd-footer-container {
- bottom: -1rem;
- margin: -1rem;
- position: fixed;
+body {
+ overflow: scroll;
}
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 81ad06e..92ad7a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -90,12 +90,12 @@ todo_include_todos = True
#
html_theme = "sphinx_book_theme"
html_theme_path = [sphinx_book_theme.get_html_theme_path()]
-#html_theme_options = {
-# "logo_only": True,
+html_theme_options = {
+ "logo_only": True,
# "collapse_navigation": False,
# "display_version": False,
-# "navigation_depth": 4,
-#}
+ "navigation_depth": 4,
+}
html_logo = "_static/img/logo/logo1.png"
diff --git a/setup.py b/setup.py
index ad35bb4..0c636b8 100644
--- a/setup.py
+++ b/setup.py
@@ -57,7 +57,7 @@ REQUIRED = [
DEV_REQUIRED = [
# For documentations
"sphinx",
- "sphinx_book_theme",
+ "sphinx_book_theme==0.3.3",
# CI dependencies
"pytest>=3",
"wheel",
From f5d9c6c6315052726cc833bc4cb83f66626dc175 Mon Sep 17 00:00:00 2001
From: Asymptotez <201220101@smail.nju.edu.cn>
Date: Wed, 29 Nov 2023 11:01:10 +0800
Subject: [PATCH 07/24] [DOC] add more details about using update_learnware()
and delete_learnware()
---
docs/workflows/client.rst | 42 +++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/docs/workflows/client.rst b/docs/workflows/client.rst
index cfdfb02..143ce64 100644
--- a/docs/workflows/client.rst
+++ b/docs/workflows/client.rst
@@ -104,6 +104,48 @@ Remember to verify the learnware before uploading it, as shown in the following
After uploading the learnware successfully, you can see it in ``My Learnware``, the background will check it. Click on the learnware, which can be viewed in the ``Verify Status``. After the check passes, the Unverified tag of the learnware will disappear, and the uploaded learnware will appear in the system.
+Update Learnware
+-------------------------------
+
+The ``update_learnware`` method is used to update the metadata and content of an existing learnware on the server. You can upload a new semantic specification, or directly upload a new learnware.
+
+.. code-block:: python
+
+ # Replace with the actual learnware ID
+ learnware_id = "123456789"
+
+ # Create new semantic specification
+ semantic_spec = client.create_semantic_specification(
+ name="new learnware name",
+ description="new description",
+ data_type="Table",
+ task_type="Classification",
+ library_type="Scikit-learn",
+ scenarios=["Computer", "Internet"],
+ input_description=new_input_description,
+ output_description=new_output_description,
+ )
+
+ # Update metadata without changing the content
+ client.update_learnware(learnware_id, semantic_spec)
+
+ # Update metadata and content with a new ZIP file
+ updated_zip_path = "/path/to/updated_learnware.zip"
+ client.update_learnware(learnware_id, semantic_spec, learnware_zip_path=updated_zip_path)
+
+Delete Learnware
+-------------------------------
+
+The ``delete_learnware`` method is used to delete a learnware from the server.
+
+.. code-block:: python
+
+ # Replace with the actual learnware ID
+ learnware_id = "123456789"
+
+ # Delete the specified learnware
+ client.delete_learnware(learnware_id)
+
Semantic Specification Search
-------------------------------
From f0b8717cb8200b739e14c26ee9d3b66a085cf382 Mon Sep 17 00:00:00 2001
From: Gene
Date: Wed, 29 Nov 2023 15:39:08 +0800
Subject: [PATCH 08/24] [FIX] change backend host
---
learnware/config.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/learnware/config.py b/learnware/config.py
index 02f3bcb..3b1954d 100644
--- a/learnware/config.py
+++ b/learnware/config.py
@@ -3,6 +3,7 @@ import copy
import logging
from enum import Enum
+
class Config:
def __init__(self, default_conf):
self.__dict__["_default_config"] = copy.deepcopy(default_conf) # avoiding conflictions with __getattr__
@@ -53,8 +54,10 @@ class SystemType(Enum):
MACOS = 1
WINDOWS = 2
+
def get_platform():
import platform
+
os_name = platform.system().lower()
if "macos" in os_name or "darwin" in os_name:
return SystemType.MACOS
@@ -64,6 +67,7 @@ def get_platform():
return SystemType.LINUX
raise SystemError("Learnware only support MACOS/Linux/Windows")
+
if get_platform() == SystemType.MACOS:
ROOT_DIRPATH = os.path.join(os.path.expanduser("~"), "Library", "Learnware")
else:
@@ -142,7 +146,7 @@ _DEFAULT_CONFIG = {
},
"database_url": f"sqlite:///{DATABASE_PATH}",
"max_reduced_set_size": 1310720,
- "backend_host": "http://www.lamda.nju.edu.cn/learnware/api",
+ "backend_host": "https://bmwu.cloud/api",
"random_seed": 0,
}
From a358f0c7e96f733e440126fbf2d65f7b090762c4 Mon Sep 17 00:00:00 2001
From: Gene
Date: Wed, 29 Nov 2023 20:18:53 +0800
Subject: [PATCH 09/24] [FIX] change error type
---
learnware/market/easy/checker.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/learnware/market/easy/checker.py b/learnware/market/easy/checker.py
index eb6fe75..7995d94 100644
--- a/learnware/market/easy/checker.py
+++ b/learnware/market/easy/checker.py
@@ -57,7 +57,7 @@ class EasySemanticChecker(BaseChecker):
return EasySemanticChecker.NONUSABLE_LEARNWARE, "EasySemanticChecker Success"
- except AssertionError as err:
+ except Exception as err:
logger.warning(f"semantic_specification is not valid due to {err}!")
return EasySemanticChecker.INVALID_LEARNWARE, traceback.format_exc()
From bad9612ab8f4339ae821de17945ad4b3ddece3de Mon Sep 17 00:00:00 2001
From: liuht
Date: Thu, 30 Nov 2023 13:47:06 +0800
Subject: [PATCH 10/24] [FIX] fix sepc typo
---
learnware/market/heterogeneous/organizer/__init__.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/learnware/market/heterogeneous/organizer/__init__.py b/learnware/market/heterogeneous/organizer/__init__.py
index 113b8c3..0258eea 100644
--- a/learnware/market/heterogeneous/organizer/__init__.py
+++ b/learnware/market/heterogeneous/organizer/__init__.py
@@ -96,7 +96,7 @@ class HeteroMapTableOrganizer(EasyOrganizer):
)
if learnwere_status == BaseChecker.USABLE_LEARWARE and len(self._get_hetero_learnware_ids(learnware_id)):
- self._update_learware_hetero_sepc(learnware_id)
+ self._update_learware_hetero_spec(learnware_id)
if self.auto_update:
self.count_down -= 1
@@ -113,7 +113,7 @@ class HeteroMapTableOrganizer(EasyOrganizer):
f"Market mapping train completed. Now update HeteroMapTableSpecification for {training_learnware_ids}"
)
self.market_mapping = updated_market_mapping
- self._update_learware_hetero_sepc(training_learnware_ids)
+ self._update_learware_hetero_spec(training_learnware_ids)
self.count_down = self.auto_update_limit
@@ -167,7 +167,7 @@ class HeteroMapTableOrganizer(EasyOrganizer):
"""
final_status = super(HeteroMapTableOrganizer, self).update_learnware(id, zip_path, semantic_spec, check_status)
if final_status == BaseChecker.USABLE_LEARWARE and len(self._get_hetero_learnware_ids(id)):
- self._update_learware_hetero_sepc(id)
+ self._update_learware_hetero_spec(id)
return final_status
def _reload_learnware_hetero_spec(self, learnware_id):
@@ -178,7 +178,7 @@ class HeteroMapTableOrganizer(EasyOrganizer):
hetero_spec.load(hetero_spec_path)
self.learnware_list[learnware_id].update_stat_spec(hetero_spec.type, hetero_spec)
else:
- self._update_learware_hetero_sepc(learnware_id)
+ self._update_learware_hetero_spec(learnware_id)
logger.info(f"Reload HeteroMapTableSpecification for hetero spec {learnware_id} succeed!")
except Exception as err:
logger.error(f"Reload HeteroMapTableSpecification for hetero spec {learnware_id} failed! due to {err}.")
@@ -196,7 +196,7 @@ class HeteroMapTableOrganizer(EasyOrganizer):
if len(self._get_hetero_learnware_ids(learnware_id)):
self._reload_learnware_hetero_spec(learnware_id)
- def _update_learware_hetero_sepc(self, ids: Union[str, List[str]]):
+ def _update_learware_hetero_spec(self, ids: Union[str, List[str]]):
"""Update learnware by ids, attempting to generate HeteroMapTableSpecification for them.
Parameters
From 1dadf41ad2619f0bab00ddc7708118a12b105f3a Mon Sep 17 00:00:00 2001
From: GeneLiuXe <356340460@qq.com>
Date: Thu, 30 Nov 2023 14:02:39 +0800
Subject: [PATCH 11/24] [FIX] fix commands to fit with windows
---
learnware/client/container.py | 15 +++++++++------
learnware/client/learnware_client.py | 2 +-
learnware/client/utils.py | 6 ++++--
3 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/learnware/client/container.py b/learnware/client/container.py
index 5573799..c92d451 100644
--- a/learnware/client/container.py
+++ b/learnware/client/container.py
@@ -106,7 +106,7 @@ class ModelCondaContainer(ModelContainer):
"-n",
f"{self.conda_env}",
"--no-capture-output",
- "python3",
+ "python",
f"{self.model_script}",
"--model-path",
f"{model_path}",
@@ -149,7 +149,7 @@ class ModelCondaContainer(ModelContainer):
"-n",
f"{self.conda_env}",
"--no-capture-output",
- "python3",
+ "python",
f"{self.model_script}",
"--model-path",
f"{model_path}",
@@ -333,10 +333,11 @@ class ModelDockerContainer(ModelContainer):
"-n",
f"{conda_env}",
"--no-capture-output",
- "python3",
+ "python",
"-m",
"pip",
"install",
+ "--user",
"-r",
f"{requirements_path_filter}",
]
@@ -362,10 +363,11 @@ class ModelDockerContainer(ModelContainer):
"-n",
f"{conda_env}",
"--no-capture-output",
- "python3",
+ "python",
"-m",
"pip",
"install",
+ "--user",
"learnware",
]
)
@@ -404,7 +406,7 @@ class ModelDockerContainer(ModelContainer):
"-n",
f"{self.conda_env}",
"--no-capture-output",
- "python3",
+ "python",
f"{self.docker_model_script_path}",
"--model-path",
f"{model_path}",
@@ -458,7 +460,7 @@ class ModelDockerContainer(ModelContainer):
"-n",
f"{self.conda_env}",
"--no-capture-output",
- "python3",
+ "python",
f"{self.docker_model_script_path}",
"--model-path",
f"{model_path}",
@@ -559,6 +561,7 @@ class LearnwaresContainer:
return self
def __exit__(self, exc_type, exc_val, exc_tb):
+ return # TODO
if not self.cleanup:
logger.warning(f"Notice, the learnware container env is not cleaned up!")
self.learnware_containers = None
diff --git a/learnware/client/learnware_client.py b/learnware/client/learnware_client.py
index f3cbe61..d442dc6 100644
--- a/learnware/client/learnware_client.py
+++ b/learnware/client/learnware_client.py
@@ -178,7 +178,7 @@ class LearnwareClient:
headers=self.headers,
stream=True,
)
-
+ print(response)
if response.status_code != 200:
raise Exception("download failed: " + json.dumps(response.json()))
diff --git a/learnware/client/utils.py b/learnware/client/utils.py
index 0e11ae0..c4c5c47 100644
--- a/learnware/client/utils.py
+++ b/learnware/client/utils.py
@@ -76,10 +76,11 @@ def install_environment(learnware_dirpath, conda_env):
"-n",
f"{conda_env}",
"--no-capture-output",
- "python3",
+ "python",
"-m",
"pip",
"install",
+ "--user",
"-r",
f"{requirements_path_filter}",
]
@@ -95,10 +96,11 @@ def install_environment(learnware_dirpath, conda_env):
"-n",
f"{conda_env}",
"--no-capture-output",
- "python3",
+ "python",
"-m",
"pip",
"install",
+ "--user",
"learnware",
]
)
From de0ce05613f727e787169dc22d5fef18ee5524d9 Mon Sep 17 00:00:00 2001
From: Gene
Date: Thu, 30 Nov 2023 15:39:52 +0800
Subject: [PATCH 12/24] [FIX] fix details
---
learnware/client/container.py | 1 -
learnware/client/learnware_client.py | 8 ++++----
.../test_check_learnware.py | 20 +++++++++----------
3 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/learnware/client/container.py b/learnware/client/container.py
index c92d451..fbef446 100644
--- a/learnware/client/container.py
+++ b/learnware/client/container.py
@@ -561,7 +561,6 @@ class LearnwaresContainer:
return self
def __exit__(self, exc_type, exc_val, exc_tb):
- return # TODO
if not self.cleanup:
logger.warning(f"Notice, the learnware container env is not cleaned up!")
self.learnware_containers = None
diff --git a/learnware/client/learnware_client.py b/learnware/client/learnware_client.py
index d442dc6..bdc6d6e 100644
--- a/learnware/client/learnware_client.py
+++ b/learnware/client/learnware_client.py
@@ -178,7 +178,7 @@ class LearnwareClient:
headers=self.headers,
stream=True,
)
- print(response)
+
if response.status_code != 200:
raise Exception("download failed: " + json.dumps(response.json()))
@@ -247,7 +247,7 @@ class LearnwareClient:
for learnware in result["data"]["learnware_list_single"]:
returns.append(
- {
+ {
"type": "single",
"learnware_id": learnware["learnware_id"],
"semantic_specification": learnware["semantic_specification"],
@@ -259,12 +259,12 @@ class LearnwareClient:
"type": "multiple",
"learnware_ids": [],
"semantic_specifications": [],
- "matching": result["data"]["learnware_list_multi"][0]["matching"]
+ "matching": result["data"]["learnware_list_multi"][0]["matching"],
}
for learnware in result["data"]["learnware_list_multi"]:
multiple_learnware["learnware_ids"].append(learnware["learnware_id"])
multiple_learnware["semantic_specifications"].append(learnware["semantic_specification"])
-
+
returns.append(multiple_learnware)
return returns
diff --git a/tests/test_learnware_client/test_check_learnware.py b/tests/test_learnware_client/test_check_learnware.py
index 59f0820..f9e6213 100644
--- a/tests/test_learnware_client/test_check_learnware.py
+++ b/tests/test_learnware_client/test_check_learnware.py
@@ -46,16 +46,16 @@ class TestCheckLearnware(unittest.TestCase):
semantic_spec = json.load(json_file)
LearnwareClient.check_learnware(self.zip_path, semantic_spec)
- def test_check_learnware_image(self):
- learnware_id = "00000677"
- with tempfile.TemporaryDirectory(prefix="learnware_") as tempdir:
- self.zip_path = os.path.join(tempdir, "test.zip")
- self.client.download_learnware(learnware_id, self.zip_path)
-
- with zipfile.ZipFile(self.zip_path, "r") as zip_file:
- with zip_file.open("semantic_specification.json") as json_file:
- semantic_spec = json.load(json_file)
- LearnwareClient.check_learnware(self.zip_path, semantic_spec)
+ # def test_check_learnware_image(self):
+ # learnware_id = "00000677"
+ # with tempfile.TemporaryDirectory(prefix="learnware_") as tempdir:
+ # self.zip_path = os.path.join(tempdir, "test.zip")
+ # self.client.download_learnware(learnware_id, self.zip_path)
+
+ # with zipfile.ZipFile(self.zip_path, "r") as zip_file:
+ # with zip_file.open("semantic_specification.json") as json_file:
+ # semantic_spec = json.load(json_file)
+ # LearnwareClient.check_learnware(self.zip_path, semantic_spec)
def test_check_learnware_text(self):
learnware_id = "00000662"
From 5317c321023e77bb92a83fdc59e1e7714eb2f012 Mon Sep 17 00:00:00 2001
From: Gene
Date: Thu, 30 Nov 2023 18:37:35 +0800
Subject: [PATCH 13/24] [FIX] fix test details
---
.../test_check_learnware.py | 20 +++++++++----------
.../test_learnware_client/test_load_conda.py | 18 ++++++++---------
.../test_learnware_client/test_load_docker.py | 10 +++++-----
3 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/tests/test_learnware_client/test_check_learnware.py b/tests/test_learnware_client/test_check_learnware.py
index f9e6213..59f0820 100644
--- a/tests/test_learnware_client/test_check_learnware.py
+++ b/tests/test_learnware_client/test_check_learnware.py
@@ -46,16 +46,16 @@ class TestCheckLearnware(unittest.TestCase):
semantic_spec = json.load(json_file)
LearnwareClient.check_learnware(self.zip_path, semantic_spec)
- # def test_check_learnware_image(self):
- # learnware_id = "00000677"
- # with tempfile.TemporaryDirectory(prefix="learnware_") as tempdir:
- # self.zip_path = os.path.join(tempdir, "test.zip")
- # self.client.download_learnware(learnware_id, self.zip_path)
-
- # with zipfile.ZipFile(self.zip_path, "r") as zip_file:
- # with zip_file.open("semantic_specification.json") as json_file:
- # semantic_spec = json.load(json_file)
- # LearnwareClient.check_learnware(self.zip_path, semantic_spec)
+ def test_check_learnware_image(self):
+ learnware_id = "00000677"
+ with tempfile.TemporaryDirectory(prefix="learnware_") as tempdir:
+ self.zip_path = os.path.join(tempdir, "test.zip")
+ self.client.download_learnware(learnware_id, self.zip_path)
+
+ with zipfile.ZipFile(self.zip_path, "r") as zip_file:
+ with zip_file.open("semantic_specification.json") as json_file:
+ semantic_spec = json.load(json_file)
+ LearnwareClient.check_learnware(self.zip_path, semantic_spec)
def test_check_learnware_text(self):
learnware_id = "00000662"
diff --git a/tests/test_learnware_client/test_load_conda.py b/tests/test_learnware_client/test_load_conda.py
index 4394348..11f7e40 100644
--- a/tests/test_learnware_client/test_load_conda.py
+++ b/tests/test_learnware_client/test_load_conda.py
@@ -16,7 +16,7 @@ class TestLearnwareLoad(unittest.TestCase):
self.client = LearnwareClient()
root = os.path.dirname(__file__)
- self.learnware_ids = ["00000084", "00000154", "00000155"]
+ self.learnware_ids = ["00000910", "00000899", "00000900"]
self.zip_paths = [os.path.join(root, x) for x in ["1.zip", "2.zip", "3.zip"]]
def test_load_single_learnware_by_zippath(self):
@@ -26,8 +26,8 @@ class TestLearnwareLoad(unittest.TestCase):
learnware_list = [
self.client.load_learnware(learnware_path=zippath, runnable_option="conda") for zippath in self.zip_paths
]
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
@@ -38,8 +38,8 @@ class TestLearnwareLoad(unittest.TestCase):
self.client.download_learnware(learnware_id, zip_path)
learnware_list = self.client.load_learnware(learnware_path=self.zip_paths, runnable_option="conda")
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
@@ -49,8 +49,8 @@ class TestLearnwareLoad(unittest.TestCase):
learnware_list = [
self.client.load_learnware(learnware_id=idx, runnable_option="conda") for idx in self.learnware_ids
]
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
@@ -58,8 +58,8 @@ class TestLearnwareLoad(unittest.TestCase):
def test_load_multi_learnware_by_id(self):
learnware_list = self.client.load_learnware(learnware_id=self.learnware_ids, runnable_option="conda")
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
diff --git a/tests/test_learnware_client/test_load_docker.py b/tests/test_learnware_client/test_load_docker.py
index 775405c..4436504 100644
--- a/tests/test_learnware_client/test_load_docker.py
+++ b/tests/test_learnware_client/test_load_docker.py
@@ -16,7 +16,7 @@ class TestLearnwareLoad(unittest.TestCase):
self.client = LearnwareClient()
root = os.path.dirname(__file__)
- self.learnware_ids = ["00000084", "00000154", "00000155"]
+ self.learnware_ids = ["00000910", "00000899", "00000900"]
self.zip_paths = [os.path.join(root, x) for x in ["1.zip", "2.zip", "3.zip"]]
def test_load_multi_learnware_by_zippath(self):
@@ -24,8 +24,8 @@ class TestLearnwareLoad(unittest.TestCase):
self.client.download_learnware(learnware_id, zip_path)
learnware_list = self.client.load_learnware(learnware_path=self.zip_paths, runnable_option="docker")
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
@@ -33,8 +33,8 @@ class TestLearnwareLoad(unittest.TestCase):
def test_load_multi_learnware_by_id(self):
learnware_list = self.client.load_learnware(learnware_id=self.learnware_ids, runnable_option="docker")
- reuser = AveragingReuser(learnware_list, mode="vote_by_label")
- input_array = np.random.random(size=(20, 13))
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
print(reuser.predict(input_array))
for learnware in learnware_list:
From a3612adcd8b9eba8485fb3f54f215a45023ad67f Mon Sep 17 00:00:00 2001
From: bxdd
Date: Thu, 30 Nov 2023 19:10:20 +0800
Subject: [PATCH 14/24] [MNT] release new beta version
---
learnware/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/learnware/__init__.py b/learnware/__init__.py
index c3f122e..eed5dde 100644
--- a/learnware/__init__.py
+++ b/learnware/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.2.0.4"
+__version__ = "0.2.0.5"
import os
import json
From e4d5a0da7ebf866aa298ffadc2715b37b85fad2d Mon Sep 17 00:00:00 2001
From: bxdd
Date: Thu, 30 Nov 2023 22:53:05 +0800
Subject: [PATCH 15/24] [FIX] fix client util bug
---
learnware/client/utils.py | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/learnware/client/utils.py b/learnware/client/utils.py
index c4c5c47..049d07f 100644
--- a/learnware/client/utils.py
+++ b/learnware/client/utils.py
@@ -10,24 +10,18 @@ logger = get_module_logger(module_name="client_utils")
def system_execute(args, timeout=None, env=None, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE):
- if env is None:
- env = os.environ.copy()
- pass
-
- if isinstance(args, str):
- pass
- else:
- args = " ".join(args)
- pass
+ env = os.environ.copy() if env is None else env
+ args = args if isinstance(args, str) else " ".join(args)
com_process = subprocess.run(args, stdout=stdout, stderr=stderr, timeout=timeout, env=env, shell=True)
try:
com_process.check_returncode()
except subprocess.CalledProcessError as err:
- errmsg = com_process.stderr.decode()
- logger.warning(f"System Execute Error: {errmsg}")
- raise Exception(errmsg)
+ if err.stderr is not None:
+ errmsg = err.stderr.decode()
+ logger.warning(f"System Execute Error: {errmsg}")
+ raise err
def remove_enviroment(conda_env):
From 09910cfc0a44181d101aca5aa05238e72247555f Mon Sep 17 00:00:00 2001
From: GeneLiuXe <356340460@qq.com>
Date: Fri, 1 Dec 2023 18:30:36 +0800
Subject: [PATCH 16/24] [FIX] fix details about pip packages
---
learnware/client/package_utils.py | 78 ++++++++++++++++++-------------
learnware/client/utils.py | 2 +
2 files changed, 47 insertions(+), 33 deletions(-)
diff --git a/learnware/client/package_utils.py b/learnware/client/package_utils.py
index 5768a4e..f3266ae 100644
--- a/learnware/client/package_utils.py
+++ b/learnware/client/package_utils.py
@@ -6,6 +6,7 @@ import tempfile
import subprocess
from typing import List, Tuple
from . import utils
+from concurrent.futures import ThreadPoolExecutor
from ..logger import get_module_logger
@@ -13,38 +14,38 @@ from ..logger import get_module_logger
logger = get_module_logger("package_utils")
-def try_to_run(args, timeout=5, retry=5):
- sucess = False
+def try_to_run(args, timeout=10, retry=3):
for i in range(retry):
try:
- utils.system_execute(args=args, timeout=timeout, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- sucess = True
- break
+ result = utils.system_execute(args=args, timeout=timeout, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
+ return result.stdout.decode()
except subprocess.TimeoutExpired as e:
pass
-
- if not sucess:
- raise subprocess.TimeoutExpired(args, timeout)
+
+ raise subprocess.TimeoutExpired(args, timeout)
def parse_pip_requirement(line: str):
- """Parse pip requirement line to package name"""
+ """Parse pip requirement line to package name and version"""
line = line.strip()
-
- if len(line) == 0:
- return None
-
- if line[0] in ("#", "-"):
+ if len(line) == 0 or line[0] in ("#", "-"):
return None
- package_str = line
- for split_ch in ("=", ">", "<", "!", "~", " "):
- split_ch_index = package_str.find(split_ch)
+ package_name, package_version = line, line
+ for split_ch in ("=", ">", "<", "!", "~", " ", "="):
+ split_ch_index = package_name.find(split_ch)
if split_ch_index != -1:
- package_str = package_str[:split_ch_index]
+ package_name = package_name[:split_ch_index]
+
+ split_ch_index = package_version.find(split_ch)
+ if split_ch_index != -1:
+ package_version = package_version[split_ch_index + 1:]
+
+ if package_version == package_name:
+ package_version = ""
- return package_str
+ return package_name, package_version
def read_pip_packages_from_requirements(requirements_file: str) -> Tuple[List[str], List[str]]:
@@ -54,7 +55,7 @@ def read_pip_packages_from_requirements(requirements_file: str) -> Tuple[List[st
lines = []
with open(requirements_file, "r") as fin:
for line in fin:
- package_str = parse_pip_requirement(line)
+ package_str, package_version = parse_pip_requirement(line)
packages.append(package_str)
lines.append(line)
@@ -70,22 +71,34 @@ def filter_nonexist_pip_packages(packages: list) -> Tuple[List[str], List[str]]:
exist_packages: list of exist packages
nonexist_packages: list of non-exist packages
"""
-
- exist_packages = []
- nonexist_packages = []
- for package in packages:
- if package is None:
- continue
+ def _filter_nonexist_pip_package_worker(package):
+ # Return filtered package
try:
- package_name = parse_pip_requirement(package)
+ package_name, package_version = parse_pip_requirement(package)
if package_name is not None and package_name != "learnware":
- try_to_run(args=["pip", "index", "versions", package_name], timeout=5)
- exist_packages.append(package)
- continue
+ result = try_to_run(args=["pip", "index", "versions", package_name], timeout=10)
+ if len(package_version) and package_version not in result:
+ return package_name
+ else:
+ return package
except Exception as e:
logger.error(e)
- nonexist_packages.append(package)
-
+
+ return None
+
+ exist_packages = []
+ nonexist_packages = []
+ packages = [package for package in packages if package is not None]
+
+ with ThreadPoolExecutor(max_workers=max(os.cpu_count() // 5, 1)) as executor:
+ results = executor.map(_filter_nonexist_pip_package_worker, packages)
+
+ for result, package in zip(list(results), packages):
+ if result is not None:
+ exist_packages.append(result)
+ else:
+ nonexist_packages.append(package)
+
return exist_packages, nonexist_packages
@@ -169,7 +182,6 @@ def read_conda_packages_from_dict(env_desc: dict) -> Tuple[List[str], List[str]]
for package in conda_packages:
if isinstance(package, dict) and "pip" in package:
pip_packages = package["pip"]
- # pip_packages = [parse_pip_requirement(line) for line in pip_packages]
elif isinstance(package, str):
conda_packages_.append(package)
diff --git a/learnware/client/utils.py b/learnware/client/utils.py
index 049d07f..85f6b45 100644
--- a/learnware/client/utils.py
+++ b/learnware/client/utils.py
@@ -22,6 +22,8 @@ def system_execute(args, timeout=None, env=None, stdout=subprocess.DEVNULL, stde
errmsg = err.stderr.decode()
logger.warning(f"System Execute Error: {errmsg}")
raise err
+
+ return com_process
def remove_enviroment(conda_env):
From d94514f21067fb037586572d7ad65408b73ace91 Mon Sep 17 00:00:00 2001
From: Asymptotez <201220101@smail.nju.edu.cn>
Date: Fri, 1 Dec 2023 18:43:01 +0800
Subject: [PATCH 17/24] [DOC] modify some expressions and add the content about
license
---
docs/workflows/client.rst | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/docs/workflows/client.rst b/docs/workflows/client.rst
index 143ce64..22d58f9 100644
--- a/docs/workflows/client.rst
+++ b/docs/workflows/client.rst
@@ -70,18 +70,20 @@ Before uploading a learnware, you'll need to prepare the semantic specification
task_type="Classification",
library_type="Scikit-learn",
scenarios=["Business", "Financial"],
+ license=["Apache-2.0"],
input_description=input_description,
output_description=output_description,
)
-Make sure that the parameter input for the semantic specification is within the range given by ``client.list_semantic_specification_values(key)`` :
+Ensure that the input parameters for the semantic specification fall within the specified ranges provided by ``client.list_semantic_specification_values(key)``:
-* data_type must in ``key=SemanticSpecificationKey.DATA_TYPE``;
-* task_type must in ``key=SemanticSpecificationKey.TASK_TYPE``;
-* library_type must in ``key=SemanticSpecificationKey.LIBRARY_TYPE``;
-* scenarios must be a subset of ``key=SemanticSpecificationKey.SENARIOES``;
-* When data_type is ``"Table"``, input description needs to be filled in;
-* When task_type is in ``["Classification", "Regression"]``, output description needs to be filled.
+* "data_type" must be within the range of ``key=SemanticSpecificationKey.DATA_TYPE``.
+* "task_type" must be within the range of ``key=SemanticSpecificationKey.TASK_TYPE``.
+* "library_type" must be within the range of ``key=SemanticSpecificationKey.LIBRARY_TYPE``.
+* "scenarios" must be a subset of ``key=SemanticSpecificationKey.SENARIOS``.
+* "license" must be a subset of ``key=SemanticSpecificationKey.LICENSE``.
+* When "data_type" is set to "Table", it is necessary to provide "input_description".
+* When "task_type" is either "Classification" or "Regression", it is necessary to provide "output_description".
Finally, the semantic specification and the zip package path of the learnware were filled in to upload the learnware.
@@ -122,6 +124,7 @@ The ``update_learnware`` method is used to update the metadata and content of an
task_type="Classification",
library_type="Scikit-learn",
scenarios=["Computer", "Internet"],
+ license=["CC-BY-4.0"],
input_description=new_input_description,
output_description=new_output_description,
)
From 155f6ee1c1233a7380d1c64b85dc7dfd48791243 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 19:46:29 +0800
Subject: [PATCH 18/24] [FIX] fix bug for pip user install
---
learnware/client/container.py | 5 +----
learnware/client/utils.py | 2 --
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/learnware/client/container.py b/learnware/client/container.py
index fbef446..48835b3 100644
--- a/learnware/client/container.py
+++ b/learnware/client/container.py
@@ -337,7 +337,6 @@ class ModelDockerContainer(ModelContainer):
"-m",
"pip",
"install",
- "--user",
"-r",
f"{requirements_path_filter}",
]
@@ -367,7 +366,6 @@ class ModelDockerContainer(ModelContainer):
"-m",
"pip",
"install",
- "--user",
"learnware",
]
)
@@ -555,14 +553,13 @@ class LearnwaresContainer:
if sum(self.results) < len(self.learnware_list):
logger.warning(
- f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! This learnware will be ignored"
+ f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! This learnwares will be ignored"
)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if not self.cleanup:
- logger.warning(f"Notice, the learnware container env is not cleaned up!")
self.learnware_containers = None
self.results = None
return
diff --git a/learnware/client/utils.py b/learnware/client/utils.py
index 85f6b45..132c9a0 100644
--- a/learnware/client/utils.py
+++ b/learnware/client/utils.py
@@ -76,7 +76,6 @@ def install_environment(learnware_dirpath, conda_env):
"-m",
"pip",
"install",
- "--user",
"-r",
f"{requirements_path_filter}",
]
@@ -96,7 +95,6 @@ def install_environment(learnware_dirpath, conda_env):
"-m",
"pip",
"install",
- "--user",
"learnware",
]
)
From 346d9cfb5cef3b932d94454ba017e194ccfe0392 Mon Sep 17 00:00:00 2001
From: Gene
Date: Fri, 1 Dec 2023 19:51:17 +0800
Subject: [PATCH 19/24] [MNT] modify typo
---
learnware/client/container.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/learnware/client/container.py b/learnware/client/container.py
index 48835b3..f1f90e7 100644
--- a/learnware/client/container.py
+++ b/learnware/client/container.py
@@ -553,7 +553,7 @@ class LearnwaresContainer:
if sum(self.results) < len(self.learnware_list):
logger.warning(
- f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! This learnwares will be ignored"
+ f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! These learnwares will be ignored"
)
return self
From 50e8f494355e375c9bfb78f532727653f5811d50 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 20:52:06 +0800
Subject: [PATCH 20/24] [FIX] make container env remove always done
---
learnware/client/container.py | 11 +++--
.../test_learnware_client/test_load_conda.py | 45 -------------------
2 files changed, 7 insertions(+), 49 deletions(-)
diff --git a/learnware/client/container.py b/learnware/client/container.py
index 48835b3..590af59 100644
--- a/learnware/client/container.py
+++ b/learnware/client/container.py
@@ -43,16 +43,19 @@ class ModelContainer(BaseModel):
"""We must set `input_shape` and `output_shape`"""
if self.build:
self.cleanup_flag = True
- self._init_env()
atexit.register(self.remove_env)
-
+ self._init_env()
self._setup_env_and_metadata()
def remove_env(self):
if self.cleanup_flag is True:
- self.cleanup_flag = False
try:
+ self.cleanup_flag = False
self._remove_env()
+ except KeyboardInterrupt:
+ self.cleanup_flag = True
+ logger.warning("The KeyboardInterrupt is ignored when removing the container env!")
+ self.remove_env()
except Exception as err:
self.cleanup_flag = True
raise err
@@ -553,7 +556,7 @@ class LearnwaresContainer:
if sum(self.results) < len(self.learnware_list):
logger.warning(
- f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! This learnwares will be ignored"
+ f"{len(self.learnware_list) - sum(results)} of {len(self.learnware_list)} learnwares init failed! These learnwares will be ignored"
)
return self
diff --git a/tests/test_learnware_client/test_load_conda.py b/tests/test_learnware_client/test_load_conda.py
index 11f7e40..f623e95 100644
--- a/tests/test_learnware_client/test_load_conda.py
+++ b/tests/test_learnware_client/test_load_conda.py
@@ -19,20 +19,6 @@ class TestLearnwareLoad(unittest.TestCase):
self.learnware_ids = ["00000910", "00000899", "00000900"]
self.zip_paths = [os.path.join(root, x) for x in ["1.zip", "2.zip", "3.zip"]]
- def test_load_single_learnware_by_zippath(self):
- for learnware_id, zip_path in zip(self.learnware_ids, self.zip_paths):
- self.client.download_learnware(learnware_id, zip_path)
-
- learnware_list = [
- self.client.load_learnware(learnware_path=zippath, runnable_option="conda") for zippath in self.zip_paths
- ]
- reuser = AveragingReuser(learnware_list, mode="mean")
- input_array = np.random.random(size=(20, 40))
- print(reuser.predict(input_array))
-
- for learnware in learnware_list:
- print(learnware.id, learnware.predict(input_array))
-
def test_load_multi_learnware_by_zippath(self):
for learnware_id, zip_path in zip(self.learnware_ids, self.zip_paths):
self.client.download_learnware(learnware_id, zip_path)
@@ -45,37 +31,6 @@ class TestLearnwareLoad(unittest.TestCase):
for learnware in learnware_list:
print(learnware.id, learnware.predict(input_array))
- def test_load_single_learnware_by_id(self):
- learnware_list = [
- self.client.load_learnware(learnware_id=idx, runnable_option="conda") for idx in self.learnware_ids
- ]
- reuser = AveragingReuser(learnware_list, mode="mean")
- input_array = np.random.random(size=(20, 40))
- print(reuser.predict(input_array))
-
- for learnware in learnware_list:
- print(learnware.id, learnware.predict(input_array))
-
- def test_load_multi_learnware_by_id(self):
- learnware_list = self.client.load_learnware(learnware_id=self.learnware_ids, runnable_option="conda")
- reuser = AveragingReuser(learnware_list, mode="mean")
- input_array = np.random.random(size=(20, 40))
- print(reuser.predict(input_array))
-
- for learnware in learnware_list:
- print(learnware.id, learnware.predict(input_array))
-
- def test_load_single_learnware_by_id_pip(self):
- learnware_id = "00000147"
- learnware = self.client.load_learnware(learnware_id=learnware_id, runnable_option="conda")
- input_array = np.random.random(size=(20, 23))
- print(learnware.predict(input_array))
-
- def test_load_single_learnware_by_id_conda(self):
- learnware_id = "00000148"
- learnware = self.client.load_learnware(learnware_id=learnware_id, runnable_option="conda")
- input_array = np.random.random(size=(20, 204))
- print(learnware.predict(input_array))
if __name__ == "__main__":
From 350786fdb7926c13ac03f79a1a13a641b0a32e54 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 20:53:19 +0800
Subject: [PATCH 21/24] [MNT] recover tests
---
.../test_learnware_client/test_load_conda.py | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/tests/test_learnware_client/test_load_conda.py b/tests/test_learnware_client/test_load_conda.py
index f623e95..11f7e40 100644
--- a/tests/test_learnware_client/test_load_conda.py
+++ b/tests/test_learnware_client/test_load_conda.py
@@ -19,6 +19,20 @@ class TestLearnwareLoad(unittest.TestCase):
self.learnware_ids = ["00000910", "00000899", "00000900"]
self.zip_paths = [os.path.join(root, x) for x in ["1.zip", "2.zip", "3.zip"]]
+ def test_load_single_learnware_by_zippath(self):
+ for learnware_id, zip_path in zip(self.learnware_ids, self.zip_paths):
+ self.client.download_learnware(learnware_id, zip_path)
+
+ learnware_list = [
+ self.client.load_learnware(learnware_path=zippath, runnable_option="conda") for zippath in self.zip_paths
+ ]
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
+ print(reuser.predict(input_array))
+
+ for learnware in learnware_list:
+ print(learnware.id, learnware.predict(input_array))
+
def test_load_multi_learnware_by_zippath(self):
for learnware_id, zip_path in zip(self.learnware_ids, self.zip_paths):
self.client.download_learnware(learnware_id, zip_path)
@@ -31,6 +45,37 @@ class TestLearnwareLoad(unittest.TestCase):
for learnware in learnware_list:
print(learnware.id, learnware.predict(input_array))
+ def test_load_single_learnware_by_id(self):
+ learnware_list = [
+ self.client.load_learnware(learnware_id=idx, runnable_option="conda") for idx in self.learnware_ids
+ ]
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
+ print(reuser.predict(input_array))
+
+ for learnware in learnware_list:
+ print(learnware.id, learnware.predict(input_array))
+
+ def test_load_multi_learnware_by_id(self):
+ learnware_list = self.client.load_learnware(learnware_id=self.learnware_ids, runnable_option="conda")
+ reuser = AveragingReuser(learnware_list, mode="mean")
+ input_array = np.random.random(size=(20, 40))
+ print(reuser.predict(input_array))
+
+ for learnware in learnware_list:
+ print(learnware.id, learnware.predict(input_array))
+
+ def test_load_single_learnware_by_id_pip(self):
+ learnware_id = "00000147"
+ learnware = self.client.load_learnware(learnware_id=learnware_id, runnable_option="conda")
+ input_array = np.random.random(size=(20, 23))
+ print(learnware.predict(input_array))
+
+ def test_load_single_learnware_by_id_conda(self):
+ learnware_id = "00000148"
+ learnware = self.client.load_learnware(learnware_id=learnware_id, runnable_option="conda")
+ input_array = np.random.random(size=(20, 204))
+ print(learnware.predict(input_array))
if __name__ == "__main__":
From 3436b88008968eae83114522b5ce50decf33a9b0 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 21:16:26 +0800
Subject: [PATCH 22/24] [DOC] fix doc link
---
docs/workflows/client.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/workflows/client.rst b/docs/workflows/client.rst
index 22d58f9..48db96e 100644
--- a/docs/workflows/client.rst
+++ b/docs/workflows/client.rst
@@ -12,7 +12,7 @@ Introduction
Prepare access token
====================
-Before using the ``Learnware Client``, you'll need to obtain a token from the `official website `_. Just login to the website and click ``Client Token`` tab in the user center.
+Before using the ``Learnware Client``, you'll need to obtain a token from the `official website `_. Just login to the website and click ``Client Token`` tab in the ``Personal Information``.
How to Use Client
@@ -143,7 +143,7 @@ The ``delete_learnware`` method is used to delete a learnware from the server.
.. code-block:: python
- # Replace with the actual learnware ID
+ # Replace with the actual learnware ID to delete
learnware_id = "123456789"
# Delete the specified learnware
From d4d0291fcbc862e7562be7dc0b5c7cb57187e2bc Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 21:16:48 +0800
Subject: [PATCH 23/24] [DOC] update doc index
---
docs/index.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/index.rst b/docs/index.rst
index 91c514f..33441a3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -39,7 +39,7 @@ Document Structure
.. toctree::
:maxdepth: 3
- :caption: CONCEPTS:
+ :caption: COMPONENTS:
Market
Learnware & Reuser
From 77dc5f9efc0ed5d1d717d6862d8a854af7bc18e1 Mon Sep 17 00:00:00 2001
From: bxdd
Date: Fri, 1 Dec 2023 21:21:27 +0800
Subject: [PATCH 24/24] [DOC] update doc
---
docs/workflows/client.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/workflows/client.rst b/docs/workflows/client.rst
index 48db96e..b76fb8e 100644
--- a/docs/workflows/client.rst
+++ b/docs/workflows/client.rst
@@ -6,7 +6,7 @@ Learnware Client
Introduction
====================
-``Learnware Client`` is a python api that provides a convenient interface for interacting with the system. You can easily use the client to upload, download and search learnwares.
+``Learnware Client`` is a ``Python API`` that provides a convenient interface for interacting with the ``BeimingWu`` system. You can easily use the client to upload, download, delete, update, and search learnwares.
Prepare access token