Skip to content

Commit 9d42227

Browse files
authored
Merge pull request #154 from mlcommons/dev
Sync Dev
2 parents 1ed6bd1 + e823c3e commit 9d42227

File tree

11 files changed

+215
-29
lines changed

11 files changed

+215
-29
lines changed

.github/workflows/test-mlc-core-actions.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ jobs:
200200
run: |
201201
mlc rm repo mlcommons@mlperf-automations -f
202202
mlc pull repo mlcommons@mlperf-automations --tag=mlperf-automations-v1.0.0
203+
204+
- name: Test 22 - Test recursive mlc pull repo
205+
run: |
206+
export MLC_REPOS=$HOME/test
207+
mlc pull repo https://github.com/GATEOverflow/GO-PDFs
208+
mlcr detect,os -j
203209
204210
test_mlc_access_core_actions:
205211

@@ -245,7 +251,7 @@ jobs:
245251
246252
- name: Test for rm cache - invalid cache entry tags(test succeeds if command fails)
247253
run: |
248-
! mlc rm cache --tags=sample,invalid,tags
254+
mlc rm cache --tags=sample,invalid,tags
249255
250256
- name: Test for rm cache when the cache folder is empty(only for mlc rm cache without specifying particular script)
251257
run: |

docs/error_codes.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Error and Warning Codes in MLCFlow
2+
3+
MLCFlow uses a standardized system for error and warning codes to provide consistent error handling across the framework. This document explains the error code system and how to use it.
4+
5+
## Overview
6+
7+
The error code system consists of two main components:
8+
9+
1. **WarningCode(1000-1007)**: Enum class for warning codes (return = 0, with warning_code field)
10+
2. **ErrorCode(2000-2007)**: Enum class for error codes (return > 0)
11+
<!--
12+
## Error Code Structure
13+
14+
Error codes are organized by category:
15+
16+
- **General errors (1000-1099)**: Common errors that can occur in any part of the system
17+
- **Script errors (1100-1199)**: Errors specific to script execution and management
18+
- **Repository errors (1200-1299)**: Errors related to repository operations
19+
- **Cache errors (1300-1399)**: Errors related to cache operations
20+
21+
## Warning Code Structure
22+
23+
Warning codes follow a similar structure:
24+
25+
- **General warnings (2000-99)**: Common warnings that can occur in any part of the system
26+
- **Script warnings (2100-2199)**: Warnings specific to script execution and management
27+
- **Repository warnings (2200-2299)**: Warnings related to repository operations
28+
- **Cache warnings (2300-2399)**: Warnings related to cache operations -->
29+
30+
## Usage
31+
32+
### In MLCFlow Framework
33+
34+
When returning an error:
35+
36+
```python
37+
from mlc.error_codes import ErrorCode, get_error_message
38+
39+
return {'return': ErrorCode.UNSUPPORTED_OS.code, 'error': get_error_message(ErrorCode.UNSUPPORTED_OS.description)}
40+
```
41+
42+
When returning a warning:
43+
44+
```python
45+
from mlc.error_codes import WarningCode, get_warning_message
46+
47+
return {'return': 0, 'warning_code': WarningCode.ELEVATED_PERMISSION_NEEDED.code, 'warning': get_warning_message(WarningCode.ELEVATED_PERMISSION_NEEDED.description)}
48+
```
49+
50+
### In Scripts
51+
52+
When checking for errors or warnings:
53+
54+
```python
55+
from mlc.error_codes import is_warning_code
56+
57+
result = mlc_cache.access({'action': 'rm', 'target': 'cache', 'tags': cache_rm_tags, 'f': True})
58+
if result['return'] != 0 and not is_warning_code(result['return']):
59+
# Handle error
60+
return result
61+
```
62+
63+
## Helper Functions
64+
65+
The error code system provides several helper functions:
66+
67+
- `get_error_message(error_code)`: Get the description for an error code
68+
- `get_warning_message(warning_code)`: Get the description for a warning code
69+
- `is_warning_code(code)`: Check if a code is a warning code
70+
- `is_error_code(code)`: Check if a code is an error code
71+
- `get_code_type(code)`: Get the type of a code (error, warning, or unknown)
72+
73+
## Adding New Error or Warning Codes
74+
75+
To add a new error or warning code, update the appropriate enum class in `mlc/error_codes.py`:
76+
77+
```python
78+
# For a new error code
79+
NEW_ERROR = (2008, "Description of the new error")
80+
81+
# For a new warning code
82+
NEW_WARNING = (1007, "Description of the new warning")
83+
```
84+
85+
Make sure to follow the category structure and use the next available code in the appropriate range.

mlc/action.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .index import Index
1414
from .repo import Repo
1515
from .item import Item
16+
from .error_codes import WarningCode
1617

1718
# Base class for actions
1819
class Action:
@@ -113,9 +114,9 @@ def is_curdir_inside_path(base_path):
113114
return res
114115
continue
115116

117+
repo_path = repo_path.strip() # Remove any extra whitespace or newlines
116118
if is_curdir_inside_path(repo_path):
117119
self.current_repo_path = repo_path
118-
repo_path = repo_path.strip() # Remove any extra whitespace or newlines
119120

120121
# Skip empty lines
121122
if not repo_path:
@@ -260,8 +261,13 @@ def add(self, i):
260261

261262
target_name = i.get('target_name', self.action_type)
262263
target_path = os.path.join(repo_path, target_name)
263-
if target_name == "cache":
264-
folder_name = f"""{i["script_alias"]}_{item_name or item_id[:8]}""" if i.get("script_alias") else item_name or item_id
264+
if target_name in ["cache", "experiment"]:
265+
extra_tags_suffix=i.get('extra_tags', '').replace(",", "-")[:15]
266+
if extra_tags_suffix != '':
267+
suffix = f"_{extra_tags_suffix}"
268+
else:
269+
suffix = ''
270+
folder_name = f"""{i["script_alias"]}{suffix}_{item_name or item_id[:8]}""" if i.get("script_alias") else item_name or item_id
265271
else:
266272
folder_name = item_name or item_id
267273

@@ -340,9 +346,10 @@ def rm(self, i):
340346
# Do not error out if fetch_all is used
341347
if inp.get("fetch_all", False) == True:
342348
logger.warning(f"{target_name} is empty! nothing to be cleared!")
343-
return {"return": 0}
349+
return {"return": 0, "warnings": [{"code": WarningCode.EMPTY_TARGET.code, "description": f"{target_name} is empty! nothing to be cleared!"}]}
344350
else:
345-
return {'return': 16, 'error': f'No {target_name} found for {inp}'}
351+
logger.warning(f"No {target_name} found for {inp}")
352+
return {'return': 0, "warnings": [{"code": WarningCode.EMPTY_TARGET.code, "description": f"No {target_name} found for {inp}"}]}
346353
elif len(res['list']) > 1:
347354
logger.info(f"More than 1 {target_name} found for {inp}:")
348355
if not i.get('all'):
@@ -405,7 +412,7 @@ def save_new_meta(self, i, item_id, item_name, target_name, item_path, repo):
405412

406413
if save_result["return"] > 0:
407414
return save_result
408-
415+
409416
self.index.add(item_meta, target_name, item_path, repo)
410417
return {'return': 0}
411418

@@ -422,7 +429,7 @@ def update(self, i):
422429
dict: Return code and message.
423430
"""
424431
# Step 1: Search for items based on input tags
425-
target_name = i.get('target_name',"cache")
432+
target_name = i.get('target_name', i.get('target', "cache"))
426433
i['target_name'] = target_name
427434
ii = i.copy()
428435

@@ -551,16 +558,16 @@ def cp(self, run_args):
551558
target_split = target_item.split(":")
552559

553560
if len(target_split) > 1:
554-
target_repo = target_split[0].strip()
555-
if target_repo == ".":
561+
target_repo_name = target_split[0].strip()
562+
if target_repo_name == ".":
556563
if not self.current_repo_path:
557564
return {'return': 1, 'error': f"""Current directory is not inside a registered MLC repo and so using ".:" is not valid"""}
558-
target_repo = self.current_repo_path
565+
target_repo_name = os.path.basename(self.current_repo_path)
559566
else:
560-
if not any(os.path.basename(repodata.path) == target_repo for repodata in self.repos):
567+
if not any(os.path.basename(repodata.path) == target_repo_name for repodata in self.repos):
561568
return {'return': 1, 'error': f"""The target repo {target_repo} is not registered in MLC. Either register in MLC by cloning from Git through command `mlc pull repo` or create repo using `mlc add repo` command and try to rerun the command again"""}
562-
target_repo_path = os.path.join(self.repos_path, target_repo)
563-
target_repo = Repo(target_repo_path)
569+
target_repo_path = os.path.join(self.repos_path, target_repo_name)
570+
target_repo = next((k for k in self.repos if os.path.basename(k.path) == target_repo_name), None)
564571
target_item_name = target_split[1].strip()
565572
else:
566573
target_repo = result.repo
@@ -710,7 +717,7 @@ def search(self, i):
710717
if target == "script":
711718
non_variation_tags = [t for t in tags_split if not t.startswith("_")]
712719
tags_to_match = non_variation_tags
713-
elif target =="cache":
720+
elif target in ["cache", "experiment"]:
714721
tags_to_match = tags_split
715722
else:
716723
return {'return': 1, 'error': f"""Target {target} not handled in mlc yet"""}

mlc/action_factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .repo_action import RepoAction
22
from .script_action import ScriptAction
33
from .cache_action import CacheAction
4+
from .experiment_action import ExperimentAction
45

56

67
# Factory to get the appropriate action class
@@ -11,5 +12,6 @@ def get_action(target, parent):
1112
actions = {
1213
'repo': RepoAction,
1314
'script': ScriptAction,
14-
'cache': CacheAction
15+
'cache': CacheAction,
16+
'experiment': ExperimentAction
1517
}

mlc/error_codes.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from enum import Enum, auto
2+
3+
class ErrorCode(Enum):
4+
"""Enum class for error codes in MLCFlow"""
5+
# General errors (2000-2007)
6+
AUTOMATION_SCRIPT_NOT_FOUND = (2000, "The specified automation script was not found")
7+
PATH_DOES_NOT_EXIST = (2001, "Provided path does not exists")
8+
FILE_NOT_FOUND = (2002, "Required file was not found")
9+
PERMISSION_DENIED = (2003, "Insufficient permission to execute the script")
10+
IO_Error = (2004, "File I/O operation failed")
11+
AUTOMATION_CUSTOM_ERROR = (2005, "Custom error triggered by the script")
12+
UNSUPPORTED_OS = (2006, "The Operating System is not supported by the script")
13+
MISSING_ENV_VARIABLE = (2007, "Required environment variables are missing")
14+
15+
def __init__(self, code, description):
16+
self.code = code
17+
self.description = description
18+
19+
class WarningCode(Enum):
20+
"""Enum class for warning codes in MLCFlow"""
21+
# General warnings (1000-1007)
22+
IO_WARNING = (1000, "File I/O operation warning")
23+
AUTOMATION_SCRIPT_NOT_TESTED = (1001, "the script is not tested on the current operatinig system or is in a development state")
24+
AUTOMATION_SCRIPT_SKIPPED = (1002, "The script has been skipped during execution")
25+
AUTOMATION_CUSTOM_ERROR = (1003, "Custom warning triggered by the script")
26+
NON_INTERACTIVE_ENV = (1004, "Non interactive environment detected")
27+
ELEVATED_PERMISSION_NEEDED = (1005, "Elevated permission needed")
28+
EMPTY_TARGET = (1006, "The specified target is empty")
29+
30+
def __init__(self, code, description):
31+
self.code = code
32+
self.description = description
33+
34+
def get_error_info(error_code):
35+
"""Get the error message for a given error code"""
36+
try:
37+
return {"error_code": ErrorCode(error_code).code, "error_message": ErrorCode(error_code).description}
38+
except ValueError:
39+
return f"Unknown error code: {error_code}"
40+
41+
def get_warning_info(warning_code):
42+
"""Get the warning message for a given warning code"""
43+
try:
44+
return {"warning_code": WarningCode(warning_code).code, "warning_message": WarningCode(warning_code).description}
45+
except ValueError:
46+
return f"Unknown warning code: {warning_code}"
47+
48+
def is_warning_code(code):
49+
"""Check if a given code is a warning code"""
50+
try:
51+
# Check if code is in warning range (2000-2399)
52+
if 2000 <= code <= 2399:
53+
WarningCode(code)
54+
return True
55+
return False
56+
except ValueError:
57+
return False
58+
59+
def is_error_code(code):
60+
"""Check if a given code is an error code"""
61+
try:
62+
# Check if code is in error range (1000-1399)
63+
if 1000 <= code <= 1399:
64+
ErrorCode(code)
65+
return True
66+
return False
67+
except ValueError:
68+
return False
69+
70+
def get_code_type(code):
71+
"""Get the type of a code (error or warning)"""
72+
if is_error_code(code):
73+
return "error"
74+
elif is_warning_code(code):
75+
return "warning"
76+
else:
77+
return "unknown"

mlc/experiment_action.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
class ExperimentAction(Action):
77
def __init__(self, parent=None):
8-
if parent is None:
9-
parent = default_parent
108
#super().__init__(parent)
119
self.parent = parent
1210
self.__dict__.update(vars(parent))

mlc/index.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def __init__(self, repos_path, repos):
3838
self.build_index()
3939

4040
def add(self, meta, folder_type, path, repo):
41+
if not repo:
42+
logger.error(f"Repo for index add for {path} is none")
43+
return
44+
4145
unique_id = meta['uid']
4246
alias = meta['alias']
4347
tags = meta['tags']

mlc/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ def process_console_output(res, target, action, run_args):
9797
else:
9898
for item in res['list']:
9999
logger.info(f"""Item path: {item.path}""")
100+
if "warnings" in res:
101+
logger.warning(f"{len(res['warnings'])} warning(s) found during the execution of the mlc command.")
102+
for warning in res["warnings"]:
103+
logger.warning(f"Warning code: {warning['code']}, Discription: {warning['description']}")
100104

101105
if default_parent is None:
102106
default_parent = Action()

mlc/repo_action.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def register_repo(self, repo_path, repo_meta):
126126

127127
if repo_meta.get('deps'):
128128
for dep in repo_meta['deps']:
129-
self.pull_repo(dep['url'], branch=dep.get('branch'), checkout=dep.get('checkout'))
129+
self.pull_repo(dep['url'], branch=dep.get('branch'), checkout=dep.get('checkout'), ignore_on_conflict=dep.get('is_alias_okay', True))
130130

131131
# Get the path to the repos.json file in $HOME/MLC
132132
repos_file_path = os.path.join(self.repos_path, 'repos.json')
@@ -248,7 +248,7 @@ def github_url_to_user_repo_format(self, url):
248248
else:
249249
return {"return": 0, "value": os.path.basename(url).replace(".git", "")}
250250

251-
def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = None, ssh = None):
251+
def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = None, ssh = None, ignore_on_conflict = False):
252252

253253
# Determine the checkout path from environment or default
254254
repo_base_path = self.repos_path # either the value will be from 'MLC_REPOS'
@@ -269,7 +269,6 @@ def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = No
269269
if res["return"] > 0:
270270
return res
271271
else:
272-
print(res)
273272
repo_url = res["url"]
274273

275274

@@ -306,8 +305,9 @@ def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = No
306305
print(local_changes.stdout.strip())
307306
return {"return": 0, "warning": f"Local changes detected in the already existing repository: {repo_path}, skipping the pull"}
308307
else:
309-
logger.info("No local changes detected. Fetching latest changes...")
310-
subprocess.run(['git', '-C', repo_path, 'fetch'], check=True)
308+
logger.info("No local changes detected. Pulling latest changes...")
309+
subprocess.run(['git', '-C', repo_path, 'pull'], check=True)
310+
logger.info("Repository successfully pulled.")
311311

312312
if tag:
313313
checkout = "tags/"+tag
@@ -317,9 +317,9 @@ def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = No
317317
logger.info(f"Checking out to {checkout} in {repo_path}...")
318318
subprocess.run(['git', '-C', repo_path, 'checkout', checkout], check=True)
319319

320-
if not tag:
321-
subprocess.run(['git', '-C', repo_path, 'pull'], check=True)
322-
logger.info("Repository successfully pulled.")
320+
#if not tag:
321+
# subprocess.run(['git', '-C', repo_path, 'pull'], check=True)
322+
# logger.info("Repository successfully pulled.")
323323

324324
logger.info("Registering the repo in repos.json")
325325

@@ -341,9 +341,13 @@ def pull_repo(self, repo_url, branch=None, checkout = None, tag = None, pat = No
341341
return {"return": 0}
342342
elif "already registered" in is_conflict["error"]:
343343
#logger.warning(is_conflict["error"])
344-
logger.info("No changes made to repos.json.")
344+
logger.debug("No changes made to repos.json.")
345345
return {"return": 0}
346346
else:
347+
if ignore_on_conflict:
348+
logger.debug("Repo alias existing. Ignoring the repo pull")
349+
return {"return": 0}
350+
347351
logger.warning(f"The repo to be cloned has conflict with the repo already in the path: {is_conflict['conflicting_path']}")
348352
self.unregister_repo(is_conflict['conflicting_path'])
349353
self.register_repo(repo_path, meta_data)

0 commit comments

Comments
 (0)