mlcflow 1.2.2__tar.gz → 1.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mlcflow-1.2.2 → mlcflow-1.2.4}/PKG-INFO +6 -7
- {mlcflow-1.2.2 → mlcflow-1.2.4}/README.md +5 -6
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/action.py +26 -9
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/cache_action.py +92 -25
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/main.py +9 -5
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/repo_action.py +111 -7
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/script_action.py +94 -16
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/PKG-INFO +6 -7
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/SOURCES.txt +2 -1
- {mlcflow-1.2.2 → mlcflow-1.2.4}/pyproject.toml +1 -1
- mlcflow-1.2.4/tests/test_cache_mark_tmp.py +73 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/LICENSE.md +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/__init__.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/__main__.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/action_factory.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/cfg_action.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/error_codes.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/experiment_action.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/index.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/item.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/logger.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/meta_schema.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/repo.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlc/utils.py +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/dependency_links.txt +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/entry_points.txt +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/requires.txt +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/mlcflow.egg-info/top_level.txt +0 -0
- {mlcflow-1.2.2 → mlcflow-1.2.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlcflow
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: An automation interface tailored for CPU/GPU benchmarking
|
|
5
5
|
Author-email: MLCommons <systems@mlcommons.org>
|
|
6
6
|
License:
|
|
@@ -204,10 +204,10 @@ Dynamic: license-file
|
|
|
204
204
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
205
205
|
[](https://pepy.tech/project/mlcflow)
|
|
206
206
|
|
|
207
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
208
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
210
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
207
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
208
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
209
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
210
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
|
|
@@ -259,7 +259,7 @@ Each target has its own set of specific actions to tailor automation workflows a
|
|
|
259
259
|
| Target | Action |
|
|
260
260
|
|--------|-----------------|
|
|
261
261
|
| script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
|
|
262
|
-
| cache | find/search, rm, show |
|
|
262
|
+
| cache | find/search, rm, show, list, prune, mark-tmp |
|
|
263
263
|
| repo | pull, search, rm, list, find/search , add |
|
|
264
264
|
|
|
265
265
|
|
|
@@ -356,4 +356,3 @@ classDiagram
|
|
|
356
356
|
Item o-- Repo
|
|
357
357
|
Automation o-- Action
|
|
358
358
|
```
|
|
359
|
-
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
4
4
|
[](https://pepy.tech/project/mlcflow)
|
|
5
5
|
|
|
6
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
7
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
9
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
6
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
7
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
8
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
9
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
|
|
@@ -58,7 +58,7 @@ Each target has its own set of specific actions to tailor automation workflows a
|
|
|
58
58
|
| Target | Action |
|
|
59
59
|
|--------|-----------------|
|
|
60
60
|
| script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
|
|
61
|
-
| cache | find/search, rm, show |
|
|
61
|
+
| cache | find/search, rm, show, list, prune, mark-tmp |
|
|
62
62
|
| repo | pull, search, rm, list, find/search , add |
|
|
63
63
|
|
|
64
64
|
|
|
@@ -155,4 +155,3 @@ classDiagram
|
|
|
155
155
|
Item o-- Repo
|
|
156
156
|
Automation o-- Action
|
|
157
157
|
```
|
|
158
|
-
|
|
@@ -185,6 +185,15 @@ class Action:
|
|
|
185
185
|
self._index = Index(self.repos_path, self.repos)
|
|
186
186
|
return self._index
|
|
187
187
|
|
|
188
|
+
def _item_from_index_entry(self, res, target_name):
|
|
189
|
+
"""Create an Item from an index entry and skip entries with invalid meta."""
|
|
190
|
+
it = Item(res['path'], res['repo'])
|
|
191
|
+
if not isinstance(it.meta, dict):
|
|
192
|
+
logger.warning(
|
|
193
|
+
f"Skipping {target_name} item at {it.path}: missing or invalid meta")
|
|
194
|
+
return None
|
|
195
|
+
return it
|
|
196
|
+
|
|
188
197
|
def __init__(self):
|
|
189
198
|
setup_logging(log_path=os.getcwd(), log_file='.mlc-log.txt')
|
|
190
199
|
self.logger = logger
|
|
@@ -728,8 +737,13 @@ class Action:
|
|
|
728
737
|
# For targets like cache, sometimes user would need to clear the entire cache folder present in the system
|
|
729
738
|
# this helps to fetch entire data pertaining to particular target
|
|
730
739
|
if fetch_all:
|
|
740
|
+
if not target_index:
|
|
741
|
+
return {'return': 0, 'list': result}
|
|
742
|
+
|
|
731
743
|
for res in target_index:
|
|
732
|
-
|
|
744
|
+
it = self._item_from_index_entry(res, target)
|
|
745
|
+
if it:
|
|
746
|
+
result.append(it)
|
|
733
747
|
return {'return': 0, 'list': result}
|
|
734
748
|
|
|
735
749
|
if not uid and not alias and i.get('details'):
|
|
@@ -775,14 +789,16 @@ class Action:
|
|
|
775
789
|
for res in target_index:
|
|
776
790
|
if (res["uid"] == uid or (alias and res["alias"] == alias)) and (
|
|
777
791
|
not item_repo or item_repo == res['repo']):
|
|
778
|
-
it =
|
|
779
|
-
|
|
780
|
-
|
|
792
|
+
it = self._item_from_index_entry(res, target)
|
|
793
|
+
if it:
|
|
794
|
+
result.append(it)
|
|
795
|
+
found = True
|
|
781
796
|
if not found and folder_name:
|
|
782
797
|
for res in target_index:
|
|
783
798
|
if os.path.basename(res["path"]) == folder_name:
|
|
784
|
-
it =
|
|
785
|
-
|
|
799
|
+
it = self._item_from_index_entry(res, target)
|
|
800
|
+
if it:
|
|
801
|
+
result.append(it)
|
|
786
802
|
else:
|
|
787
803
|
tags = i.get("tags")
|
|
788
804
|
if tags:
|
|
@@ -803,11 +819,12 @@ class Action:
|
|
|
803
819
|
n_tags = [p[1:] for p in n_tags_]
|
|
804
820
|
p_tags = list(set(tags_to_match) - set(n_tags_))
|
|
805
821
|
for res in target_index:
|
|
806
|
-
c_tags = res
|
|
822
|
+
c_tags = res.get("tags") or []
|
|
807
823
|
if (exact_tags_match and set(p_tags) == set(c_tags)) or (not exact_tags_match and set(
|
|
808
824
|
p_tags).issubset(set(c_tags)) and set(n_tags).isdisjoint(set(c_tags))):
|
|
809
|
-
it =
|
|
810
|
-
|
|
825
|
+
it = self._item_from_index_entry(res, target)
|
|
826
|
+
if it:
|
|
827
|
+
result.append(it)
|
|
811
828
|
return {'return': 0, 'list': result}
|
|
812
829
|
|
|
813
830
|
find = search
|
|
@@ -5,6 +5,7 @@ import time
|
|
|
5
5
|
from . import utils
|
|
6
6
|
from .logger import logger
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
class CacheAction(Action):
|
|
9
10
|
"""
|
|
10
11
|
####################################################################################################################
|
|
@@ -15,14 +16,16 @@ class CacheAction(Action):
|
|
|
15
16
|
2. show
|
|
16
17
|
3. list
|
|
17
18
|
4. remove(rm)
|
|
19
|
+
5. prune
|
|
20
|
+
6. mark-tmp
|
|
18
21
|
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
def __init__(self, parent=None):
|
|
22
|
-
#super().__init__(parent)
|
|
25
|
+
# super().__init__(parent)
|
|
23
26
|
self.parent = parent
|
|
24
27
|
self.__dict__.update(vars(parent))
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
def search(self, i):
|
|
27
30
|
"""
|
|
28
31
|
####################################################################################################################
|
|
@@ -30,11 +33,11 @@ class CacheAction(Action):
|
|
|
30
33
|
Action: Find (Alias: Search)
|
|
31
34
|
####################################################################################################################
|
|
32
35
|
|
|
33
|
-
The `find` (or `search`) action retrieves the path of cache generated by a particular script.
|
|
36
|
+
The `find` (or `search`) action retrieves the path of cache generated by a particular script.
|
|
34
37
|
|
|
35
38
|
Syntax:
|
|
36
39
|
|
|
37
|
-
mlc find cache --tags=<list_of_tag_used_to_run_the_particular_script>
|
|
40
|
+
mlc find cache --tags=<list_of_tag_used_to_run_the_particular_script>
|
|
38
41
|
|
|
39
42
|
Example Command:
|
|
40
43
|
|
|
@@ -42,7 +45,7 @@ class CacheAction(Action):
|
|
|
42
45
|
|
|
43
46
|
"""
|
|
44
47
|
i['target_name'] = "cache"
|
|
45
|
-
#logger.debug(f"Searching for cache with input: {i}")
|
|
48
|
+
# logger.debug(f"Searching for cache with input: {i}")
|
|
46
49
|
r = self.parent.search(i)
|
|
47
50
|
if r['return'] > 0:
|
|
48
51
|
return r
|
|
@@ -83,13 +86,13 @@ class CacheAction(Action):
|
|
|
83
86
|
|
|
84
87
|
Syntax:
|
|
85
88
|
|
|
86
|
-
mlc rm cache --tags=<list_of_tag_used_to_run_the_particular_script>
|
|
89
|
+
mlc rm cache --tags=<list_of_tag_used_to_run_the_particular_script>
|
|
87
90
|
|
|
88
91
|
Options:
|
|
89
92
|
1. `-f`: Force removes caches without confirmation. Without `-f`, the user will be prompted for confirmation before deletion.
|
|
90
93
|
|
|
91
94
|
To remove all generated caches, use:
|
|
92
|
-
|
|
95
|
+
|
|
93
96
|
mlc rm cache
|
|
94
97
|
|
|
95
98
|
Example Command:
|
|
@@ -98,9 +101,68 @@ class CacheAction(Action):
|
|
|
98
101
|
|
|
99
102
|
"""
|
|
100
103
|
i['target_name'] = "cache"
|
|
101
|
-
#logger.debug(f"Removing cache with input: {i}")
|
|
104
|
+
# logger.debug(f"Removing cache with input: {i}")
|
|
102
105
|
return self.parent.rm(i)
|
|
103
106
|
|
|
107
|
+
def mark_tmp(self, i):
|
|
108
|
+
"""
|
|
109
|
+
####################################################################################################################
|
|
110
|
+
Target: Cache
|
|
111
|
+
Action: Mark Tmp (mark-tmp)
|
|
112
|
+
####################################################################################################################
|
|
113
|
+
|
|
114
|
+
The `mark-tmp` action marks one or more cache entries as `tmp` without removing the cache contents.
|
|
115
|
+
|
|
116
|
+
Syntax:
|
|
117
|
+
|
|
118
|
+
mlc mark-tmp cache --tags=<list_of_tags_used_to_run_the_particular_script>
|
|
119
|
+
|
|
120
|
+
Example Command:
|
|
121
|
+
|
|
122
|
+
mlc mark-tmp cache --tags=get,dataset,igbh
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
i['target_name'] = "cache"
|
|
126
|
+
res = self.search(i)
|
|
127
|
+
if res['return'] > 0:
|
|
128
|
+
return res
|
|
129
|
+
|
|
130
|
+
if len(res['list']) == 0:
|
|
131
|
+
logger.warning(f"No cache found for {i}")
|
|
132
|
+
return {'return': 0, "warnings": [
|
|
133
|
+
{"code": "EMPTY_TARGET", "description": f"No cache found for {i}"}]}
|
|
134
|
+
|
|
135
|
+
updated_count = 0
|
|
136
|
+
|
|
137
|
+
for item in res['list']:
|
|
138
|
+
tags = item.meta.get("tags", [])
|
|
139
|
+
if 'tmp' in tags:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
tags.append('tmp')
|
|
143
|
+
item.meta["tags"] = tags
|
|
144
|
+
meta_yaml_path = os.path.join(item.path, "meta.yaml")
|
|
145
|
+
meta_json_path = os.path.join(item.path, "meta.json")
|
|
146
|
+
|
|
147
|
+
if os.path.exists(meta_yaml_path):
|
|
148
|
+
save_result = utils.save_yaml(meta_yaml_path, meta=item.meta)
|
|
149
|
+
else:
|
|
150
|
+
save_result = utils.save_json(meta_json_path, meta=item.meta)
|
|
151
|
+
|
|
152
|
+
if save_result['return'] > 0:
|
|
153
|
+
return save_result
|
|
154
|
+
|
|
155
|
+
self.get_index().update(item.meta, "cache", item.path, item.repo)
|
|
156
|
+
updated_count += 1
|
|
157
|
+
|
|
158
|
+
logger.info(f"Marked {updated_count} cache item(s) as tmp")
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
'return': 0,
|
|
162
|
+
'message': f"Marked {updated_count} cache item(s) as tmp",
|
|
163
|
+
'list': res['list']
|
|
164
|
+
}
|
|
165
|
+
|
|
104
166
|
def show(self, run_args):
|
|
105
167
|
"""
|
|
106
168
|
####################################################################################################################
|
|
@@ -109,9 +171,9 @@ class CacheAction(Action):
|
|
|
109
171
|
####################################################################################################################
|
|
110
172
|
|
|
111
173
|
Retrieves the path and metadata of caches generated while running scripts through MLCFlow.
|
|
112
|
-
|
|
174
|
+
|
|
113
175
|
Syntax:
|
|
114
|
-
|
|
176
|
+
|
|
115
177
|
mlc show cache --tags=<list_of_tags_used_while_running_script>
|
|
116
178
|
|
|
117
179
|
Example Command:
|
|
@@ -128,7 +190,11 @@ class CacheAction(Action):
|
|
|
128
190
|
return res
|
|
129
191
|
|
|
130
192
|
logger.info(f"Showing cache with tags: {run_args.get('tags')}")
|
|
131
|
-
cached_meta_keys_to_show = [
|
|
193
|
+
cached_meta_keys_to_show = [
|
|
194
|
+
"uid",
|
|
195
|
+
"tags",
|
|
196
|
+
"dependent_cached_path",
|
|
197
|
+
"associated_script_item"]
|
|
132
198
|
cached_state_keys_to_show = ["new_env", "new_state", "version"]
|
|
133
199
|
for item in res['list']:
|
|
134
200
|
print(f"""Location: {item.path}:
|
|
@@ -137,7 +203,8 @@ Cache Meta:""")
|
|
|
137
203
|
if key in item.meta:
|
|
138
204
|
print(f""" {key}: {item.meta[key]}""")
|
|
139
205
|
print("""Cached State:""")
|
|
140
|
-
cached_state_meta_file = os.path.join(
|
|
206
|
+
cached_state_meta_file = os.path.join(
|
|
207
|
+
item.path, "mlc-cached-state.json")
|
|
141
208
|
if not os.path.exists(cached_state_meta_file):
|
|
142
209
|
continue
|
|
143
210
|
try:
|
|
@@ -149,17 +216,16 @@ Cache Meta:""")
|
|
|
149
216
|
print(f""" {key}:""", end="")
|
|
150
217
|
if meta[key] and isinstance(meta[key], dict):
|
|
151
218
|
print("")
|
|
152
|
-
utils.printd(
|
|
219
|
+
utils.printd(
|
|
220
|
+
meta[key], yaml=False, sort_keys=True, begin_spaces=8)
|
|
153
221
|
else:
|
|
154
222
|
print(f""" {meta[key]}""")
|
|
155
223
|
except json.JSONDecodeError as e:
|
|
156
224
|
logger.error(f"Error decoding JSON: {e}")
|
|
157
225
|
print("......................................................")
|
|
158
226
|
print("")
|
|
159
|
-
|
|
160
|
-
return {'return': 0}
|
|
161
227
|
|
|
162
|
-
|
|
228
|
+
return {'return': 0}
|
|
163
229
|
|
|
164
230
|
def prune(self, args):
|
|
165
231
|
"""
|
|
@@ -176,12 +242,13 @@ Cache Meta:""")
|
|
|
176
242
|
|
|
177
243
|
"""
|
|
178
244
|
self.action_type = "cache"
|
|
179
|
-
|
|
180
|
-
|
|
245
|
+
# to fetch the details of all the caches generated
|
|
246
|
+
run_args = {"fetch_all": True}
|
|
247
|
+
|
|
181
248
|
res = self.search(run_args)
|
|
182
249
|
if res['return'] > 0:
|
|
183
250
|
return res
|
|
184
|
-
|
|
251
|
+
|
|
185
252
|
for item in res['list']:
|
|
186
253
|
expiration_time = item.meta.get('cache_expiration')
|
|
187
254
|
if expiration_time is not None and expiration_time < time.time():
|
|
@@ -192,8 +259,6 @@ Cache Meta:""")
|
|
|
192
259
|
self.rm(ii)
|
|
193
260
|
|
|
194
261
|
return {'return': 0}
|
|
195
|
-
|
|
196
|
-
|
|
197
262
|
|
|
198
263
|
def list(self, args):
|
|
199
264
|
"""
|
|
@@ -210,16 +275,18 @@ Cache Meta:""")
|
|
|
210
275
|
|
|
211
276
|
"""
|
|
212
277
|
self.action_type = "cache"
|
|
213
|
-
|
|
214
|
-
|
|
278
|
+
# to fetch the details of all the caches generated
|
|
279
|
+
run_args = {"fetch_all": True}
|
|
280
|
+
|
|
215
281
|
res = self.search(run_args)
|
|
216
282
|
if res['return'] > 0:
|
|
217
283
|
return res
|
|
218
|
-
|
|
284
|
+
|
|
219
285
|
logger.info(f"Listing all the caches and their paths")
|
|
220
286
|
print("......................................................")
|
|
221
287
|
for item in res['list']:
|
|
222
|
-
print(
|
|
288
|
+
print(
|
|
289
|
+
f"tags: {item.meta['tags'] if item.meta.get('tags') else 'None'}")
|
|
223
290
|
print(f"Location: {item.path}")
|
|
224
291
|
print("......................................................")
|
|
225
292
|
|
|
@@ -348,7 +348,7 @@ def build_parser(pre_args):
|
|
|
348
348
|
|
|
349
349
|
# General commands
|
|
350
350
|
for action in ['run', 'pull', 'test', 'add', 'show', 'list',
|
|
351
|
-
'find', 'search', 'rm', 'cp', 'mv', 'help', 'prune']:
|
|
351
|
+
'find', 'search', 'rm', 'cp', 'mv', 'help', 'prune', 'mark-tmp']:
|
|
352
352
|
p = subparsers.add_parser(action, add_help=False)
|
|
353
353
|
p.add_argument('target', choices=['repo', 'repos', 'script', 'cache'])
|
|
354
354
|
p.add_argument(
|
|
@@ -495,7 +495,7 @@ def main():
|
|
|
495
495
|
| Target | Actions |
|
|
496
496
|
|---------|-----------------------------------------------------------|
|
|
497
497
|
| script | run, find/search, rm, mv, cp, add, test, docker-run, show |
|
|
498
|
-
| cache | find/search, rm, show
|
|
498
|
+
| cache | find/search, rm, show, list, prune, mark-tmp |
|
|
499
499
|
| repo | pull, search, rm, list, find/search |
|
|
500
500
|
|
|
501
501
|
Example:
|
|
@@ -528,6 +528,8 @@ def main():
|
|
|
528
528
|
|
|
529
529
|
pre_parser = build_pre_parser()
|
|
530
530
|
pre_args, remaining_args = pre_parser.parse_known_args()
|
|
531
|
+
if pre_args.action:
|
|
532
|
+
pre_args.action = pre_args.action.replace("-", "_")
|
|
531
533
|
|
|
532
534
|
parser = build_parser(pre_args)
|
|
533
535
|
# Force full parsing for reindex command even without target, or if there
|
|
@@ -570,11 +572,13 @@ def main():
|
|
|
570
572
|
help_text += method.__doc__
|
|
571
573
|
elif pre_args.action and pre_args.target:
|
|
572
574
|
actions = get_action(pre_args.target, default_parent)
|
|
575
|
+
action_name = pre_args.action.replace("-", "_")
|
|
573
576
|
try:
|
|
574
|
-
method = getattr(actions,
|
|
577
|
+
method = getattr(actions, action_name)
|
|
575
578
|
help_text += actions.__doc__
|
|
576
|
-
|
|
577
|
-
|
|
579
|
+
if method.__doc__:
|
|
580
|
+
help_text += method.__doc__
|
|
581
|
+
except AttributeError:
|
|
578
582
|
logger.error(
|
|
579
583
|
f"Error: '{pre_args.action}' is not supported for {pre_args.target}.")
|
|
580
584
|
if help_text != "":
|
|
@@ -323,7 +323,7 @@ class RepoAction(Action):
|
|
|
323
323
|
url).replace(".git", "")}
|
|
324
324
|
|
|
325
325
|
def pull_repo(self, repo_url, branch=None, checkout=None, tag=None,
|
|
326
|
-
pat=None, ssh=None, ignore_on_conflict=False, repo_path=None):
|
|
326
|
+
pat=None, ssh=None, ignore_on_conflict=False, repo_path=None, force=False):
|
|
327
327
|
|
|
328
328
|
# Determine the checkout path from environment or default
|
|
329
329
|
repo_base_path = self.repos_path # either the value will be from 'MLC_REPOS'
|
|
@@ -391,11 +391,104 @@ class RepoAction(Action):
|
|
|
391
391
|
status_command, capture_output=True, text=True)
|
|
392
392
|
|
|
393
393
|
if local_changes.stdout.strip():
|
|
394
|
+
if not force:
|
|
395
|
+
logger.warning(
|
|
396
|
+
"There are local changes in the repository. Please commit or stash them before checking out.")
|
|
397
|
+
print(local_changes.stdout.strip())
|
|
398
|
+
return {
|
|
399
|
+
"return": 0, "warning": f"Local changes detected in the already existing repository: {repo_path}, skipping the pull"}
|
|
400
|
+
|
|
394
401
|
logger.warning(
|
|
395
|
-
"
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
402
|
+
"Local changes detected. Running force pull with temporary git stash.")
|
|
403
|
+
stash_created = False
|
|
404
|
+
try:
|
|
405
|
+
stash_before = subprocess.run(
|
|
406
|
+
['git', '-C', repo_path, 'stash', 'list'],
|
|
407
|
+
capture_output=True,
|
|
408
|
+
text=True,
|
|
409
|
+
check=True
|
|
410
|
+
)
|
|
411
|
+
stash_res = subprocess.run(
|
|
412
|
+
['git', '-C', repo_path, 'stash', 'push',
|
|
413
|
+
'-m', 'mlc pull repo --force'],
|
|
414
|
+
capture_output=True,
|
|
415
|
+
text=True,
|
|
416
|
+
check=True
|
|
417
|
+
)
|
|
418
|
+
stash_after = subprocess.run(
|
|
419
|
+
['git', '-C', repo_path, 'stash', 'list'],
|
|
420
|
+
capture_output=True,
|
|
421
|
+
text=True,
|
|
422
|
+
check=True
|
|
423
|
+
)
|
|
424
|
+
stash_created = len(stash_after.stdout.splitlines()
|
|
425
|
+
) > len(stash_before.stdout.splitlines())
|
|
426
|
+
except subprocess.CalledProcessError as e:
|
|
427
|
+
stash_error = (e.stderr or e.stdout or str(e)).strip()
|
|
428
|
+
return {
|
|
429
|
+
"return": 1,
|
|
430
|
+
"error": f"Force pull failed while stashing local changes in {repo_path}: {stash_error}"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
logger.info(
|
|
434
|
+
"Pulling latest changes...")
|
|
435
|
+
try:
|
|
436
|
+
subprocess.run(
|
|
437
|
+
['git', '-C', repo_path, 'pull'],
|
|
438
|
+
capture_output=True,
|
|
439
|
+
text=True,
|
|
440
|
+
check=True)
|
|
441
|
+
except subprocess.CalledProcessError as e:
|
|
442
|
+
pull_error = (e.stderr or e.stdout or str(e)).strip()
|
|
443
|
+
if stash_created:
|
|
444
|
+
return {
|
|
445
|
+
"return": 1,
|
|
446
|
+
"error": f"Force pull failed during git pull for {repo_path}. Local changes remain in stash. Please run `git -C {repo_path} stash apply` after resolving pull issues. Details: {pull_error}"
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
"return": 1,
|
|
450
|
+
"error": f"Force pull failed during git pull for {repo_path}: {pull_error}"
|
|
451
|
+
}
|
|
452
|
+
logger.info("Repository successfully pulled.")
|
|
453
|
+
|
|
454
|
+
if stash_created:
|
|
455
|
+
try:
|
|
456
|
+
subprocess.run(
|
|
457
|
+
['git', '-C', repo_path, 'stash', 'apply'],
|
|
458
|
+
capture_output=True,
|
|
459
|
+
text=True,
|
|
460
|
+
check=True)
|
|
461
|
+
subprocess.run(
|
|
462
|
+
['git', '-C', repo_path, 'stash', 'drop'],
|
|
463
|
+
capture_output=True,
|
|
464
|
+
text=True,
|
|
465
|
+
check=True)
|
|
466
|
+
logger.info(
|
|
467
|
+
"Local changes restored successfully after force pull.")
|
|
468
|
+
except subprocess.CalledProcessError as apply_error:
|
|
469
|
+
apply_error_msg = (
|
|
470
|
+
apply_error.stderr or apply_error.stdout or str(apply_error)).strip()
|
|
471
|
+
try:
|
|
472
|
+
subprocess.run(
|
|
473
|
+
['git', '-C', repo_path,
|
|
474
|
+
'reset', '--hard', 'HEAD'],
|
|
475
|
+
capture_output=True,
|
|
476
|
+
text=True,
|
|
477
|
+
check=True)
|
|
478
|
+
except subprocess.CalledProcessError as reset_exception:
|
|
479
|
+
reset_error_msg = (
|
|
480
|
+
reset_exception.stderr or reset_exception.stdout or str(reset_exception)).strip()
|
|
481
|
+
return {
|
|
482
|
+
"return": 1,
|
|
483
|
+
"error": f"Stash apply conflicted and automatic rollback failed for {repo_path}: {reset_error_msg}. Original stash apply error: {apply_error_msg}"
|
|
484
|
+
}
|
|
485
|
+
logger.warning(
|
|
486
|
+
f"Stash apply reported conflicts after pull. Reverted partial stash apply. "
|
|
487
|
+
f"Please resolve manually with `git -C {repo_path} stash apply`.")
|
|
488
|
+
return {
|
|
489
|
+
"return": 0,
|
|
490
|
+
"warning": f"Force pull succeeded for {repo_path}, but stash apply had conflicts. Partial apply was reverted. Please apply the stash manually."
|
|
491
|
+
}
|
|
399
492
|
else:
|
|
400
493
|
logger.info(
|
|
401
494
|
"No local changes detected. Pulling latest changes...")
|
|
@@ -468,6 +561,7 @@ class RepoAction(Action):
|
|
|
468
561
|
- `--branch <branch_name>`: Checks out a specific branch **while cloning** a new repository.
|
|
469
562
|
- `--tag <release_tag>`: Checks out a particular release tag.
|
|
470
563
|
- `--pat <access_token>` or `--ssh`: Clones a private repository using a personal access token or SSH.
|
|
564
|
+
- `--force`: For existing repositories with local tracked changes, stashes changes before pull and reapplies them after pull.
|
|
471
565
|
|
|
472
566
|
Example Output:
|
|
473
567
|
|
|
@@ -498,7 +592,7 @@ class RepoAction(Action):
|
|
|
498
592
|
repo_object.path, os.W_OK):
|
|
499
593
|
repo_folder_name = os.path.basename(repo_object.path)
|
|
500
594
|
res = self.pull_repo(
|
|
501
|
-
repo_folder_name, repo_path=repo_object.path)
|
|
595
|
+
repo_folder_name, repo_path=repo_object.path, force=run_args.get('force'))
|
|
502
596
|
if res['return'] > 0:
|
|
503
597
|
return res
|
|
504
598
|
else:
|
|
@@ -508,12 +602,22 @@ class RepoAction(Action):
|
|
|
508
602
|
|
|
509
603
|
pat = run_args.get('pat')
|
|
510
604
|
ssh = run_args.get('ssh')
|
|
605
|
+
force = run_args.get('force')
|
|
606
|
+
ignore_on_conflict = run_args.get('ignore_on_conflict')
|
|
511
607
|
|
|
512
608
|
if sum(bool(var) for var in [branch, checkout, tag]) > 1:
|
|
513
609
|
return {
|
|
514
610
|
"return": 1, "error": "Only one among the three flags(branch, checkout and tag) could be specified"}
|
|
515
611
|
|
|
516
|
-
res = self.pull_repo(
|
|
612
|
+
res = self.pull_repo(
|
|
613
|
+
repo_url,
|
|
614
|
+
branch,
|
|
615
|
+
checkout,
|
|
616
|
+
tag,
|
|
617
|
+
pat,
|
|
618
|
+
ssh,
|
|
619
|
+
ignore_on_conflict=ignore_on_conflict,
|
|
620
|
+
force=force)
|
|
517
621
|
if res['return'] > 0:
|
|
518
622
|
return res
|
|
519
623
|
|
|
@@ -377,25 +377,37 @@ Main Script Meta:""")
|
|
|
377
377
|
Flags Available:
|
|
378
378
|
|
|
379
379
|
1. --docker_dt or --docker_detached:
|
|
380
|
-
Runs the specified script inside a Docker container in detached mode
|
|
380
|
+
Runs the specified script inside a Docker container in detached mode.
|
|
381
381
|
By default, the Docker container is launched in interactive mode.
|
|
382
382
|
2. --docker_cache:
|
|
383
|
-
Disabling this flag forces Docker to build all layers from scratch, ignoring cached layers
|
|
384
|
-
By default, the value is set to true/yes.
|
|
383
|
+
Disabling this flag forces Docker to build all layers from scratch, ignoring cached layers (default: yes)
|
|
385
384
|
3. --docker_rebuild:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
385
|
+
Rebuilds the Docker image even if one with the same tag already exists (default: False)
|
|
386
|
+
4. --docker_noregenerate:
|
|
387
|
+
Skip regeneration of the Dockerfile during execution (default: False)
|
|
388
|
+
5. --docker_image_repo:
|
|
389
|
+
Custom Docker image repository name
|
|
390
|
+
6. --docker_verbose:
|
|
391
|
+
Enable verbose output during Docker operations
|
|
392
|
+
7. --docker_silent:
|
|
393
|
+
Suppress output during Docker operations
|
|
394
|
+
8. --docker_host_mlc_repos:
|
|
395
|
+
Mount host MLC repos inside the container
|
|
396
|
+
9. --docker_upload:
|
|
397
|
+
Push the built Docker image after execution
|
|
398
|
+
10. --docker_run_cmd_prefix:
|
|
399
|
+
Prefix to prepend to the run command inside the container
|
|
391
400
|
|
|
392
401
|
Example Command:
|
|
393
402
|
|
|
394
403
|
mlc docker script --tags=detect,os -j
|
|
404
|
+
mlcd detect,os -j
|
|
395
405
|
|
|
396
406
|
"""
|
|
397
407
|
return self.call_script_module_function("docker", run_args)
|
|
398
408
|
|
|
409
|
+
docker.__doc__ = docker_run.__doc__
|
|
410
|
+
|
|
399
411
|
def remote_run(self, run_args):
|
|
400
412
|
"""
|
|
401
413
|
####################################################################################################################
|
|
@@ -409,13 +421,32 @@ Main Script Meta:""")
|
|
|
409
421
|
Flags Available:
|
|
410
422
|
|
|
411
423
|
1. --remote_host:
|
|
412
|
-
IP or
|
|
424
|
+
IP or hostname for the remote machine (default: localhost)
|
|
413
425
|
2. --remote_port:
|
|
414
|
-
|
|
426
|
+
SSH port for the remote machine (default: 22)
|
|
427
|
+
3. --remote_user:
|
|
428
|
+
Username for SSH login on the remote machine
|
|
429
|
+
4. --remote_password:
|
|
430
|
+
Password for SSH authentication
|
|
431
|
+
5. --remote_ssh_key_file:
|
|
432
|
+
Path to the SSH private key file for authentication
|
|
433
|
+
6. --remote_skip_host_verify:
|
|
434
|
+
Skip SSH host key verification
|
|
435
|
+
7. --remote_python_venv:
|
|
436
|
+
Name of the Python virtual environment on the remote machine (default: mlcflow)
|
|
437
|
+
8. --remote_pull_mlc_repos:
|
|
438
|
+
Pull MLC repos on the remote machine before running
|
|
439
|
+
9. --remote_copy_directory:
|
|
440
|
+
Remote directory to copy files to (default: mlc-remote-artifacts)
|
|
441
|
+
10. --remote_pre_run_cmds:
|
|
442
|
+
Commands to run on the remote machine before the main script
|
|
443
|
+
11. --remote_client_refresh:
|
|
444
|
+
Refresh the SSH client connection
|
|
415
445
|
|
|
416
446
|
Example Command:
|
|
417
447
|
|
|
418
448
|
mlc remote-run script --tags=detect,os -j
|
|
449
|
+
mlcrr detect,os -j
|
|
419
450
|
|
|
420
451
|
"""
|
|
421
452
|
return self.call_script_module_function("remote_run", run_args)
|
|
@@ -539,12 +570,23 @@ Main Script Meta:""")
|
|
|
539
570
|
Action: Experiment
|
|
540
571
|
####################################################################################################################
|
|
541
572
|
|
|
542
|
-
The `experiment` action
|
|
543
|
-
|
|
573
|
+
The `experiment` action automates exploration runs of MLC scripts.
|
|
574
|
+
|
|
575
|
+
Flags Available:
|
|
576
|
+
|
|
577
|
+
1. --exp_tags:
|
|
578
|
+
Comma-separated extra tags for the experiment run
|
|
579
|
+
2. --exp_skip_state_save:
|
|
580
|
+
Skip saving the system state during the experiment (default: False)
|
|
581
|
+
3. --exp.<key>=<value>:
|
|
582
|
+
Pass experiment-specific parameters using the `exp.` prefix (e.g., --exp.batch_size=32)
|
|
583
|
+
|
|
584
|
+
In addition, all flags supported by the `run` action are also available.
|
|
544
585
|
|
|
545
586
|
Example Command:
|
|
546
587
|
|
|
547
588
|
mlc experiment script --tags=detect,os -j
|
|
589
|
+
mlce detect,os -j
|
|
548
590
|
|
|
549
591
|
"""
|
|
550
592
|
return self.call_script_module_function("experiment", run_args)
|
|
@@ -561,9 +603,27 @@ Main Script Meta:""")
|
|
|
561
603
|
Flags Available:
|
|
562
604
|
|
|
563
605
|
1. --remote_host:
|
|
564
|
-
IP or hostname for the remote machine
|
|
606
|
+
IP or hostname for the remote machine (default: localhost)
|
|
565
607
|
2. --remote_port:
|
|
566
|
-
|
|
608
|
+
SSH port for the remote machine (default: 22)
|
|
609
|
+
3. --remote_user:
|
|
610
|
+
Username for SSH login on the remote machine
|
|
611
|
+
4. --remote_password:
|
|
612
|
+
Password for SSH authentication
|
|
613
|
+
5. --remote_ssh_key_file:
|
|
614
|
+
Path to the SSH private key file for authentication
|
|
615
|
+
6. --remote_skip_host_verify:
|
|
616
|
+
Skip SSH host key verification
|
|
617
|
+
7. --remote_python_venv:
|
|
618
|
+
Name of the Python virtual environment on the remote machine (default: mlcflow)
|
|
619
|
+
8. --remote_pull_mlc_repos:
|
|
620
|
+
Pull MLC repos on the remote machine before running
|
|
621
|
+
9. --remote_copy_directory:
|
|
622
|
+
Remote directory to copy files to (default: mlc-remote-artifacts)
|
|
623
|
+
10. --remote_pre_run_cmds:
|
|
624
|
+
Commands to run on the remote machine before the main script
|
|
625
|
+
11. --remote_client_refresh:
|
|
626
|
+
Refresh the SSH client connection
|
|
567
627
|
|
|
568
628
|
Example Command:
|
|
569
629
|
|
|
@@ -586,9 +646,27 @@ Main Script Meta:""")
|
|
|
586
646
|
Flags Available:
|
|
587
647
|
|
|
588
648
|
1. --remote_host:
|
|
589
|
-
IP or hostname for the remote machine
|
|
649
|
+
IP or hostname for the remote machine (default: localhost)
|
|
590
650
|
2. --remote_port:
|
|
591
|
-
|
|
651
|
+
SSH port for the remote machine (default: 22)
|
|
652
|
+
3. --remote_user:
|
|
653
|
+
Username for SSH login on the remote machine
|
|
654
|
+
4. --remote_password:
|
|
655
|
+
Password for SSH authentication
|
|
656
|
+
5. --remote_ssh_key_file:
|
|
657
|
+
Path to the SSH private key file for authentication
|
|
658
|
+
6. --remote_skip_host_verify:
|
|
659
|
+
Skip SSH host key verification
|
|
660
|
+
7. --remote_python_venv:
|
|
661
|
+
Name of the Python virtual environment on the remote machine (default: mlcflow)
|
|
662
|
+
8. --remote_pull_mlc_repos:
|
|
663
|
+
Pull MLC repos on the remote machine before running
|
|
664
|
+
9. --remote_copy_directory:
|
|
665
|
+
Remote directory to copy files to (default: mlc-remote-artifacts)
|
|
666
|
+
10. --remote_pre_run_cmds:
|
|
667
|
+
Commands to run on the remote machine before the main script
|
|
668
|
+
11. --remote_client_refresh:
|
|
669
|
+
Refresh the SSH client connection
|
|
592
670
|
|
|
593
671
|
Example Command:
|
|
594
672
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlcflow
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: An automation interface tailored for CPU/GPU benchmarking
|
|
5
5
|
Author-email: MLCommons <systems@mlcommons.org>
|
|
6
6
|
License:
|
|
@@ -204,10 +204,10 @@ Dynamic: license-file
|
|
|
204
204
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
205
205
|
[](https://pepy.tech/project/mlcflow)
|
|
206
206
|
|
|
207
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
208
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
210
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
207
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
208
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
209
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
210
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
|
|
@@ -259,7 +259,7 @@ Each target has its own set of specific actions to tailor automation workflows a
|
|
|
259
259
|
| Target | Action |
|
|
260
260
|
|--------|-----------------|
|
|
261
261
|
| script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
|
|
262
|
-
| cache | find/search, rm, show |
|
|
262
|
+
| cache | find/search, rm, show, list, prune, mark-tmp |
|
|
263
263
|
| repo | pull, search, rm, list, find/search , add |
|
|
264
264
|
|
|
265
265
|
|
|
@@ -356,4 +356,3 @@ classDiagram
|
|
|
356
356
|
Item o-- Repo
|
|
357
357
|
Automation o-- Action
|
|
358
358
|
```
|
|
359
|
-
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
from mlc.action import Action
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MarkTmpCacheCliTest(unittest.TestCase):
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
17
|
+
self.addCleanup(self.temp_dir.cleanup)
|
|
18
|
+
self.previous_cwd = os.getcwd()
|
|
19
|
+
self.addCleanup(os.chdir, self.previous_cwd)
|
|
20
|
+
os.chdir(self.temp_dir.name)
|
|
21
|
+
|
|
22
|
+
self.previous_mlc_repos = os.environ.get("MLC_REPOS")
|
|
23
|
+
self.addCleanup(self._restore_env)
|
|
24
|
+
os.environ["MLC_REPOS"] = os.path.join(self.temp_dir.name, "repos")
|
|
25
|
+
|
|
26
|
+
action = Action()
|
|
27
|
+
action.parent = None
|
|
28
|
+
res = action.add({
|
|
29
|
+
"target_name": "cache",
|
|
30
|
+
"item": "sample-cache",
|
|
31
|
+
"tags": "get,dataset,igbh"
|
|
32
|
+
})
|
|
33
|
+
self.assertEqual(res["return"], 0)
|
|
34
|
+
self.cache_path = res["path"]
|
|
35
|
+
|
|
36
|
+
def _restore_env(self):
|
|
37
|
+
if self.previous_mlc_repos is None:
|
|
38
|
+
os.environ.pop("MLC_REPOS", None)
|
|
39
|
+
else:
|
|
40
|
+
os.environ["MLC_REPOS"] = self.previous_mlc_repos
|
|
41
|
+
|
|
42
|
+
def _run_cli(self):
|
|
43
|
+
env = os.environ.copy()
|
|
44
|
+
existing_pythonpath = env.get("PYTHONPATH")
|
|
45
|
+
env["PYTHONPATH"] = REPO_ROOT if not existing_pythonpath else REPO_ROOT + \
|
|
46
|
+
os.pathsep + existing_pythonpath
|
|
47
|
+
return subprocess.run(
|
|
48
|
+
[sys.executable, "-m", "mlc.main", "mark-tmp",
|
|
49
|
+
"cache", "--tags=get,dataset,igbh"],
|
|
50
|
+
cwd=self.temp_dir.name,
|
|
51
|
+
env=env,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
check=False
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def test_mark_tmp_adds_tmp_tag_without_duplicates(self):
|
|
58
|
+
first_run = self._run_cli()
|
|
59
|
+
self.assertEqual(first_run.returncode, 0, msg=first_run.stderr)
|
|
60
|
+
|
|
61
|
+
second_run = self._run_cli()
|
|
62
|
+
self.assertEqual(second_run.returncode, 0, msg=second_run.stderr)
|
|
63
|
+
|
|
64
|
+
with open(os.path.join(self.cache_path, "meta.json"), "r") as file:
|
|
65
|
+
meta = json.load(file)
|
|
66
|
+
|
|
67
|
+
self.assertIn("tmp", meta["tags"])
|
|
68
|
+
self.assertEqual(meta["tags"].count("tmp"), 1)
|
|
69
|
+
self.assertTrue(os.path.isdir(self.cache_path))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|