mlcflow 1.2.6__tar.gz → 1.2.7__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.
Files changed (33) hide show
  1. {mlcflow-1.2.6 → mlcflow-1.2.7}/PKG-INFO +1 -1
  2. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/main.py +2 -2
  3. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/repo_action.py +192 -31
  4. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/script_action.py +2 -1
  5. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/PKG-INFO +1 -1
  6. {mlcflow-1.2.6 → mlcflow-1.2.7}/pyproject.toml +1 -1
  7. mlcflow-1.2.7/tests/test_script_action_apptainer.py +54 -0
  8. mlcflow-1.2.6/tests/test_script_action_apptainer.py +0 -29
  9. {mlcflow-1.2.6 → mlcflow-1.2.7}/LICENSE.md +0 -0
  10. {mlcflow-1.2.6 → mlcflow-1.2.7}/README.md +0 -0
  11. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/__init__.py +0 -0
  12. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/__main__.py +0 -0
  13. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/action.py +0 -0
  14. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/action_factory.py +0 -0
  15. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/cache_action.py +0 -0
  16. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/cfg_action.py +0 -0
  17. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/error_codes.py +0 -0
  18. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/experiment_action.py +0 -0
  19. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/index.py +0 -0
  20. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/item.py +0 -0
  21. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/logger.py +0 -0
  22. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/meta_schema.py +0 -0
  23. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/repo.py +0 -0
  24. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlc/utils.py +0 -0
  25. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/SOURCES.txt +0 -0
  26. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/dependency_links.txt +0 -0
  27. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/entry_points.txt +0 -0
  28. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/requires.txt +0 -0
  29. {mlcflow-1.2.6 → mlcflow-1.2.7}/mlcflow.egg-info/top_level.txt +0 -0
  30. {mlcflow-1.2.6 → mlcflow-1.2.7}/setup.cfg +0 -0
  31. {mlcflow-1.2.6 → mlcflow-1.2.7}/tests/test_action_invalid_meta_entries.py +0 -0
  32. {mlcflow-1.2.6 → mlcflow-1.2.7}/tests/test_cache_mark_tmp.py +0 -0
  33. {mlcflow-1.2.6 → mlcflow-1.2.7}/tests/test_mlcflow_unix_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -576,7 +576,7 @@ def main():
576
576
  else:
577
577
  pre_args.target, pre_args.action = pre_args.action, None
578
578
  actions = get_action(pre_args.target, default_parent)
579
- help_text += actions.__doc__
579
+ help_text += actions.__doc__ or ""
580
580
  # iterate through every method
581
581
  for method_name, method in inspect.getmembers(
582
582
  actions.__class__, inspect.isfunction):
@@ -588,7 +588,7 @@ def main():
588
588
  action_name = pre_args.action.replace("-", "_")
589
589
  try:
590
590
  method = getattr(actions, action_name)
591
- help_text += actions.__doc__
591
+ help_text += actions.__doc__ or ""
592
592
  if method.__doc__:
593
593
  help_text += method.__doc__
594
594
  except AttributeError:
@@ -40,11 +40,152 @@ class RepoAction(Action):
40
40
 
41
41
  """
42
42
 
43
+ GIT_MISSING_BRANCH_PHRASE = "did not match any file(s) known to git"
44
+ GIT_FAST_FORWARD_FAILURE_PHRASE = "not possible to fast-forward"
45
+
43
46
  def __init__(self, parent=None):
44
47
  # super().__init__(parent)
45
48
  self.parent = parent
46
49
  self.__dict__.update(vars(parent))
47
50
 
51
+ def _build_pull_command(self, repo_path, branch=None, clone_depth=None,
52
+ fast_forward_only=False):
53
+ pull_command = ['git', '-C', repo_path, 'pull']
54
+ if fast_forward_only:
55
+ pull_command.append('--ff-only')
56
+ if clone_depth is not None:
57
+ if self._is_shallow_repo(repo_path):
58
+ pull_command.extend(['--depth', str(clone_depth)])
59
+ else:
60
+ logger.warning(
61
+ f"--depth/--shallow ignored for pull on non-shallow repo {repo_path}. "
62
+ "Re-clone with --shallow to create a shallow copy."
63
+ )
64
+ # Preserve the historical existing-repo behavior of a plain
65
+ # `git pull` by default. The branch argument is only forwarded to
66
+ # `git pull` when the internal strict path explicitly opts into
67
+ # fast-forward-only updates.
68
+ if branch and fast_forward_only:
69
+ pull_command.extend(['origin', branch])
70
+ return pull_command
71
+
72
+ def _checkout_pull_branch(self, repo_path, branch):
73
+ """Ensure *branch* is checked out locally before a strict pull.
74
+
75
+ First tries to checkout an existing local branch. If that fails, fetches
76
+ the branch from origin and creates a new tracking branch. Raises
77
+ RuntimeError with contextual guidance when the branch cannot be prepared.
78
+
79
+ Args:
80
+ repo_path: Local repository path.
81
+ branch: Branch name to prepare before pulling.
82
+
83
+ Raises:
84
+ RuntimeError: If the branch cannot be fetched or checked out.
85
+ """
86
+ try:
87
+ subprocess.run(
88
+ ['git', '-C', repo_path, 'checkout', branch],
89
+ capture_output=True,
90
+ text=True,
91
+ check=True)
92
+ except subprocess.CalledProcessError as checkout_error:
93
+ checkout_error_text = self._subprocess_error_message(
94
+ checkout_error)
95
+ lowered_checkout_error = checkout_error_text.lower()
96
+ # Git uses the same exit code for many checkout failures, so we
97
+ # only fall back to fetch+track when stderr includes both
98
+ # "pathspec" and Git's usual missing-ref phrase
99
+ # "did not match any file(s) known to git".
100
+ missing_local_branch = (
101
+ "pathspec" in lowered_checkout_error
102
+ and self.GIT_MISSING_BRANCH_PHRASE in lowered_checkout_error
103
+ )
104
+ if not missing_local_branch:
105
+ raise RuntimeError(
106
+ f"Cannot switch to branch '{branch}' in {repo_path}: "
107
+ f"{checkout_error_text}"
108
+ ) from checkout_error
109
+ try:
110
+ subprocess.run(
111
+ ['git', '-C', repo_path, 'fetch', 'origin', branch],
112
+ capture_output=True,
113
+ text=True,
114
+ check=True)
115
+ except subprocess.CalledProcessError as fetch_error:
116
+ raise RuntimeError(
117
+ f"Failed to fetch branch '{branch}' in {repo_path}: "
118
+ f"{self._subprocess_error_message(fetch_error)}. "
119
+ "Ensure the branch exists on origin and that the repository is reachable."
120
+ ) from fetch_error
121
+
122
+ try:
123
+ subprocess.run(
124
+ ['git', '-C', repo_path, 'checkout', '-b',
125
+ branch, '--track', f'origin/{branch}'],
126
+ capture_output=True,
127
+ text=True,
128
+ check=True)
129
+ except subprocess.CalledProcessError as tracking_error:
130
+ raise RuntimeError(
131
+ f"Initial checkout of '{branch}' failed in {repo_path}. "
132
+ "After fetching from origin, creating a tracking branch also failed. "
133
+ f"Initial error: {checkout_error_text}. "
134
+ f"Tracking branch error: {self._subprocess_error_message(tracking_error)}. "
135
+ "Check that the branch name is correct and that your local checkout can track origin."
136
+ ) from tracking_error
137
+
138
+ @staticmethod
139
+ def _format_stash_restore_guidance(repo_path):
140
+ return (
141
+ f"Local changes remain in stash. Please run `git -C {repo_path} stash apply` "
142
+ "after resolving pull issues."
143
+ )
144
+
145
+ def _format_pull_error(self, repo_path, error_message, stash_created=False,
146
+ force=False, failure_phase="git pull"):
147
+ """Format a pull failure message.
148
+
149
+ Args:
150
+ repo_path: Repository path being updated.
151
+ error_message: Original stderr/stdout-derived git failure text.
152
+ stash_created: Whether local changes were stashed for a force pull.
153
+ force: Whether the failure happened on a force-pull path.
154
+ failure_phase: Phase label such as ``git pull`` or
155
+ ``branch checkout``.
156
+
157
+ Returns:
158
+ A user-facing error string with optional recovery guidance.
159
+ """
160
+ prefix = "Force pull failed" if force else "Pull failed"
161
+ if failure_phase:
162
+ prefix = f"{prefix} during {failure_phase}"
163
+
164
+ resolution = ""
165
+ lowered_error = error_message.lower()
166
+ # Git reports ff-only failures via stderr text instead of a distinct
167
+ # exit code, so use this case-insensitive substring from the standard
168
+ # "Not possible to fast-forward, aborting." message to add guidance.
169
+ if self.GIT_FAST_FORWARD_FAILURE_PHRASE in lowered_error:
170
+ resolution = (
171
+ " Check whether the local and remote branches have diverged "
172
+ "and reconcile them manually before retrying."
173
+ )
174
+
175
+ if stash_created:
176
+ return (
177
+ f"{prefix} for {repo_path}. "
178
+ f"{self._format_stash_restore_guidance(repo_path)} "
179
+ f"Details: {error_message}{resolution}"
180
+ )
181
+
182
+ return f"{prefix} for {repo_path}: {error_message}{resolution}"
183
+
184
+ @staticmethod
185
+ def _subprocess_error_message(error):
186
+ """Return the most helpful message from a subprocess-related error."""
187
+ return (error.stderr or error.stdout or str(error)).strip()
188
+
48
189
  def add(self, run_args):
49
190
  """
50
191
  ####################################################################################################################
@@ -337,7 +478,7 @@ class RepoAction(Action):
337
478
 
338
479
  def pull_repo(self, repo_url, branch=None, checkout=None, tag=None,
339
480
  pat=None, ssh=None, ignore_on_conflict=False, repo_path=None, force=False,
340
- shallow=False, depth=None, extra_git_args=None):
481
+ shallow=False, depth=None, extra_git_args=None, fast_forward_only=False):
341
482
 
342
483
  # Determine the checkout path from environment or default
343
484
  repo_base_path = self.repos_path # either the value will be from 'MLC_REPOS'
@@ -468,30 +609,33 @@ class RepoAction(Action):
468
609
  logger.info(
469
610
  "Pulling latest changes...")
470
611
  try:
471
- pull_command = ['git', '-C', repo_path, 'pull']
472
- if clone_depth is not None:
473
- if self._is_shallow_repo(repo_path):
474
- pull_command += ['--depth', str(clone_depth)]
475
- else:
476
- logger.warning(
477
- f"--depth/--shallow ignored for pull on non-shallow repo {repo_path}. "
478
- "Re-clone with --shallow to create a shallow copy."
479
- )
612
+ if fast_forward_only and branch:
613
+ self._checkout_pull_branch(repo_path, branch)
480
614
  subprocess.run(
481
- pull_command,
615
+ self._build_pull_command(
616
+ repo_path, branch, clone_depth, fast_forward_only),
482
617
  capture_output=True,
483
618
  text=True,
484
619
  check=True)
620
+ except RuntimeError as e:
621
+ return {
622
+ "return": 1,
623
+ "error": self._format_pull_error(
624
+ repo_path,
625
+ str(e),
626
+ stash_created=stash_created,
627
+ force=True,
628
+ failure_phase="branch checkout")
629
+ }
485
630
  except subprocess.CalledProcessError as e:
486
- pull_error = (e.stderr or e.stdout or str(e)).strip()
487
- if stash_created:
488
- return {
489
- "return": 1,
490
- "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}"
491
- }
631
+ pull_error = self._subprocess_error_message(e)
492
632
  return {
493
633
  "return": 1,
494
- "error": f"Force pull failed during git pull for {repo_path}: {pull_error}"
634
+ "error": self._format_pull_error(
635
+ repo_path,
636
+ pull_error,
637
+ stash_created=stash_created,
638
+ force=True)
495
639
  }
496
640
  logger.info("Repository successfully pulled.")
497
641
 
@@ -536,16 +680,28 @@ class RepoAction(Action):
536
680
  else:
537
681
  logger.info(
538
682
  "No local changes detected. Pulling latest changes...")
539
- pull_command = ['git', '-C', repo_path, 'pull']
540
- if clone_depth is not None:
541
- if self._is_shallow_repo(repo_path):
542
- pull_command += ['--depth', str(clone_depth)]
543
- else:
544
- logger.warning(
545
- f"--depth/--shallow ignored for pull on non-shallow repo {repo_path}. "
546
- "Re-clone with --shallow to create a shallow copy."
547
- )
548
- subprocess.run(pull_command, check=True)
683
+ try:
684
+ if fast_forward_only and branch:
685
+ self._checkout_pull_branch(repo_path, branch)
686
+ subprocess.run(
687
+ self._build_pull_command(
688
+ repo_path, branch, clone_depth, fast_forward_only),
689
+ capture_output=True,
690
+ text=True,
691
+ check=True)
692
+ except RuntimeError as e:
693
+ return {
694
+ "return": 1,
695
+ "error": self._format_pull_error(
696
+ repo_path, str(e), failure_phase="branch checkout")
697
+ }
698
+ except subprocess.CalledProcessError as e:
699
+ pull_error = self._subprocess_error_message(e)
700
+ return {
701
+ "return": 1,
702
+ "error": self._format_pull_error(
703
+ repo_path, pull_error)
704
+ }
549
705
  logger.info("Repository successfully pulled.")
550
706
 
551
707
  if tag:
@@ -585,6 +741,8 @@ class RepoAction(Action):
585
741
 
586
742
  return {"return": 0}
587
743
 
744
+ except RuntimeError as e:
745
+ return {'return': 1, 'error': str(e)}
588
746
  except subprocess.CalledProcessError as e:
589
747
  return {'return': 1, 'error': f"Git command failed: {e}"}
590
748
  except Exception as e:
@@ -610,7 +768,7 @@ class RepoAction(Action):
610
768
 
611
769
 
612
770
  - `--checkout <commit_sha>`: Checks out a specific commit after cloning (applicable when the repository exists locally).
613
- - `--branch <branch_name>`: Checks out a specific branch **while cloning** a new repository.
771
+ - `--branch <branch_name>`: Checks out a specific branch while cloning a new repository. When strict fast-forward-only pulls are requested for an existing checkout, it also selects the local branch to switch to before pulling `origin/<branch>`.
614
772
  - `--tag <release_tag>`: Checks out a particular release tag.
615
773
  - `--pat <access_token>` or `--ssh`: Clones a private repository using a personal access token or SSH.
616
774
  - `--force`: For existing repositories with local tracked changes, stashes changes before pull and reapplies them after pull.
@@ -651,7 +809,8 @@ class RepoAction(Action):
651
809
  'force'),
652
810
  shallow=run_args.get('shallow', False),
653
811
  depth=run_args.get('depth'),
654
- extra_git_args=run_args.get('extra_git_args'))
812
+ extra_git_args=run_args.get('extra_git_args'),
813
+ fast_forward_only=run_args.get('fast_forward_only', False))
655
814
  if res['return'] > 0:
656
815
  return res
657
816
  else:
@@ -666,6 +825,7 @@ class RepoAction(Action):
666
825
  shallow = run_args.get('shallow', False)
667
826
  depth = run_args.get('depth')
668
827
  extra_git_args = run_args.get('extra_git_args')
828
+ fast_forward_only = run_args.get('fast_forward_only', False)
669
829
 
670
830
  if sum(bool(var) for var in [branch, checkout, tag]) > 1:
671
831
  return {
@@ -682,7 +842,8 @@ class RepoAction(Action):
682
842
  force=force,
683
843
  shallow=shallow,
684
844
  depth=depth,
685
- extra_git_args=extra_git_args)
845
+ extra_git_args=extra_git_args,
846
+ fast_forward_only=fast_forward_only)
686
847
  if res['return'] > 0:
687
848
  return res
688
849
 
@@ -244,7 +244,8 @@ Main Script Meta:""")
244
244
  "automation": "repo",
245
245
  "action": "pull",
246
246
  "repo": "mlcommons@mlperf-automations",
247
- "branch": "dev"
247
+ "branch": "dev",
248
+ "fast_forward_only": True
248
249
  })
249
250
 
250
251
  if result['return'] == 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mlcflow"
7
- version = "1.2.6"
7
+ version = "1.2.7"
8
8
 
9
9
  description = "An automation interface tailored for CPU/GPU benchmarking"
10
10
  authors = [
@@ -0,0 +1,54 @@
1
+ import tempfile
2
+ import unittest
3
+ from unittest.mock import patch
4
+
5
+ from mlc.script_action import ScriptAction
6
+
7
+
8
+ class _Parent:
9
+ def __init__(self, repos_path=None):
10
+ self.repos_path = repos_path
11
+
12
+
13
+ class ScriptActionApptainerTest(unittest.TestCase):
14
+ def test_apptainer_delegates_to_script_module(self):
15
+ action = ScriptAction(_Parent(tempfile.gettempdir()))
16
+ run_args = {"tags": "detect,os"}
17
+ expected = {"return": 0}
18
+
19
+ with patch.object(
20
+ ScriptAction,
21
+ "call_script_module_function",
22
+ return_value=expected) as call_script_module_function:
23
+ result = action.apptainer(run_args)
24
+
25
+ self.assertEqual(result, expected)
26
+ call_script_module_function.assert_called_once_with(
27
+ "apptainer", run_args)
28
+
29
+ def test_auto_pull_uses_fast_forward_only_for_mlperf_automations(self):
30
+ with tempfile.TemporaryDirectory() as repos_path:
31
+ action = ScriptAction(_Parent(repos_path))
32
+
33
+ with patch.object(
34
+ ScriptAction,
35
+ "find_target_folder",
36
+ return_value=None), \
37
+ patch.object(
38
+ ScriptAction,
39
+ "access",
40
+ return_value={"return": 1, "error": "pull failed"}) as access:
41
+ result = action.call_script_module_function("run", {})
42
+
43
+ self.assertEqual(result["return"], 1)
44
+ access.assert_called_once_with({
45
+ "automation": "repo",
46
+ "action": "pull",
47
+ "repo": "mlcommons@mlperf-automations",
48
+ "branch": "dev",
49
+ "fast_forward_only": True
50
+ })
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()
@@ -1,29 +0,0 @@
1
- import unittest
2
- from unittest.mock import patch
3
-
4
- from mlc.script_action import ScriptAction
5
-
6
-
7
- class _Parent:
8
- pass
9
-
10
-
11
- class ScriptActionApptainerTest(unittest.TestCase):
12
- def test_apptainer_delegates_to_script_module(self):
13
- action = ScriptAction(_Parent())
14
- run_args = {"tags": "detect,os"}
15
- expected = {"return": 0}
16
-
17
- with patch.object(
18
- ScriptAction,
19
- "call_script_module_function",
20
- return_value=expected) as call_script_module_function:
21
- result = action.apptainer(run_args)
22
-
23
- self.assertEqual(result, expected)
24
- call_script_module_function.assert_called_once_with(
25
- "apptainer", run_args)
26
-
27
-
28
- if __name__ == "__main__":
29
- 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