mlcflow 1.2.5__tar.gz → 1.2.6__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 (32) hide show
  1. {mlcflow-1.2.5 → mlcflow-1.2.6}/PKG-INFO +1 -1
  2. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/repo_action.py +80 -15
  3. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/PKG-INFO +1 -1
  4. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/SOURCES.txt +1 -0
  5. {mlcflow-1.2.5 → mlcflow-1.2.6}/pyproject.toml +1 -1
  6. mlcflow-1.2.6/tests/test_mlcflow_unix_installer.py +83 -0
  7. {mlcflow-1.2.5 → mlcflow-1.2.6}/LICENSE.md +0 -0
  8. {mlcflow-1.2.5 → mlcflow-1.2.6}/README.md +0 -0
  9. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/__init__.py +0 -0
  10. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/__main__.py +0 -0
  11. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/action.py +0 -0
  12. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/action_factory.py +0 -0
  13. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/cache_action.py +0 -0
  14. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/cfg_action.py +0 -0
  15. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/error_codes.py +0 -0
  16. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/experiment_action.py +0 -0
  17. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/index.py +0 -0
  18. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/item.py +0 -0
  19. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/logger.py +0 -0
  20. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/main.py +0 -0
  21. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/meta_schema.py +0 -0
  22. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/repo.py +0 -0
  23. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/script_action.py +0 -0
  24. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlc/utils.py +0 -0
  25. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/dependency_links.txt +0 -0
  26. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/entry_points.txt +0 -0
  27. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/requires.txt +0 -0
  28. {mlcflow-1.2.5 → mlcflow-1.2.6}/mlcflow.egg-info/top_level.txt +0 -0
  29. {mlcflow-1.2.5 → mlcflow-1.2.6}/setup.cfg +0 -0
  30. {mlcflow-1.2.5 → mlcflow-1.2.6}/tests/test_action_invalid_meta_entries.py +0 -0
  31. {mlcflow-1.2.5 → mlcflow-1.2.6}/tests/test_cache_mark_tmp.py +0 -0
  32. {mlcflow-1.2.5 → mlcflow-1.2.6}/tests/test_script_action_apptainer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -2,6 +2,7 @@ from .action import Action
2
2
  import os
3
3
  import subprocess
4
4
  import re
5
+ import shlex
5
6
  import yaml
6
7
  import json
7
8
  import shutil
@@ -322,8 +323,21 @@ class RepoAction(Action):
322
323
  return {"return": 0, "value": os.path.basename(
323
324
  url).replace(".git", "")}
324
325
 
326
+ def _is_shallow_repo(self, repo_path):
327
+ """Return True if the git repository at *repo_path* is a shallow clone."""
328
+ try:
329
+ result = subprocess.run(
330
+ ['git', '-C', repo_path, 'rev-parse', '--is-shallow-repository'],
331
+ capture_output=True,
332
+ text=True,
333
+ )
334
+ return result.returncode == 0 and result.stdout.strip() == 'true'
335
+ except (subprocess.SubprocessError, FileNotFoundError, OSError):
336
+ return False
337
+
325
338
  def pull_repo(self, repo_url, branch=None, checkout=None, tag=None,
326
- pat=None, ssh=None, ignore_on_conflict=False, repo_path=None, force=False):
339
+ pat=None, ssh=None, ignore_on_conflict=False, repo_path=None, force=False,
340
+ shallow=False, depth=None, extra_git_args=None):
327
341
 
328
342
  # Determine the checkout path from environment or default
329
343
  repo_base_path = self.repos_path # either the value will be from 'MLC_REPOS'
@@ -358,20 +372,41 @@ class RepoAction(Action):
358
372
  repo_path = os.path.join(repo_base_path, repo_download_name)
359
373
 
360
374
  try:
375
+ # Compute depth argument: --shallow implies depth=1; explicit
376
+ # --depth=N takes precedence
377
+ clone_depth = None
378
+ if depth is not None:
379
+ try:
380
+ clone_depth = int(depth)
381
+ except (TypeError, ValueError):
382
+ return {
383
+ "return": 1, "error": f"Invalid value for --depth: {depth!r}. Must be a positive integer."}
384
+ if clone_depth < 1:
385
+ return {
386
+ "return": 1, "error": f"Invalid value for --depth: {clone_depth}. Must be a positive integer."}
387
+ elif shallow:
388
+ clone_depth = 1
389
+
390
+ # Parse extra_git_args into a list
391
+ extra_args = []
392
+ if extra_git_args:
393
+ if isinstance(extra_git_args, list):
394
+ extra_args = extra_git_args
395
+ else:
396
+ extra_args = shlex.split(str(extra_git_args))
397
+
361
398
  # If the directory doesn't exist, clone it
362
399
  if not os.path.exists(repo_path):
363
400
  logger.info(f"Cloning repository {repo_url} to {repo_path}...")
364
401
 
365
- # Build clone command without branch if not provided
366
- clone_command = ['git', 'clone', repo_url, repo_path]
402
+ # Build clone command
403
+ clone_command = ['git', 'clone']
367
404
  if branch:
368
- clone_command = [
369
- 'git',
370
- 'clone',
371
- '--branch',
372
- branch,
373
- repo_url,
374
- repo_path]
405
+ clone_command += ['--branch', branch]
406
+ if clone_depth is not None:
407
+ clone_command += ['--depth', str(clone_depth)]
408
+ clone_command += extra_args
409
+ clone_command += [repo_url, repo_path]
375
410
 
376
411
  subprocess.run(clone_command, check=True)
377
412
 
@@ -433,8 +468,17 @@ class RepoAction(Action):
433
468
  logger.info(
434
469
  "Pulling latest changes...")
435
470
  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
+ )
436
480
  subprocess.run(
437
- ['git', '-C', repo_path, 'pull'],
481
+ pull_command,
438
482
  capture_output=True,
439
483
  text=True,
440
484
  check=True)
@@ -492,8 +536,16 @@ class RepoAction(Action):
492
536
  else:
493
537
  logger.info(
494
538
  "No local changes detected. Pulling latest changes...")
495
- subprocess.run(
496
- ['git', '-C', repo_path, 'pull'], check=True)
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)
497
549
  logger.info("Repository successfully pulled.")
498
550
 
499
551
  if tag:
@@ -562,6 +614,9 @@ class RepoAction(Action):
562
614
  - `--tag <release_tag>`: Checks out a particular release tag.
563
615
  - `--pat <access_token>` or `--ssh`: Clones a private repository using a personal access token or SSH.
564
616
  - `--force`: For existing repositories with local tracked changes, stashes changes before pull and reapplies them after pull.
617
+ - `--shallow`: Perform a shallow clone with `--depth=1` (fastest for a fresh copy without history). For existing repos, only applied if the repo is already shallow; otherwise ignored with a warning.
618
+ - `--depth=N`: Perform a shallow clone/pull with the specified history depth (e.g. `--depth=5`). For existing repos, `--depth` is only applied when the repository is already a shallow clone; passing `--depth` to a full-history clone would corrupt it and is therefore ignored with a warning.
619
+ - `--extra_git_args=<args>`: Pass additional arguments to the `git clone` command (e.g. `--extra_git_args="--filter=blob:none"`). Only applies when cloning a new repository; not used for pull on existing repos. Accepts only trusted input — arguments are passed directly to git without further validation.
565
620
 
566
621
  Example Output:
567
622
 
@@ -592,7 +647,11 @@ class RepoAction(Action):
592
647
  repo_object.path, os.W_OK):
593
648
  repo_folder_name = os.path.basename(repo_object.path)
594
649
  res = self.pull_repo(
595
- repo_folder_name, repo_path=repo_object.path, force=run_args.get('force'))
650
+ repo_folder_name, repo_path=repo_object.path, force=run_args.get(
651
+ 'force'),
652
+ shallow=run_args.get('shallow', False),
653
+ depth=run_args.get('depth'),
654
+ extra_git_args=run_args.get('extra_git_args'))
596
655
  if res['return'] > 0:
597
656
  return res
598
657
  else:
@@ -604,6 +663,9 @@ class RepoAction(Action):
604
663
  ssh = run_args.get('ssh')
605
664
  force = run_args.get('force')
606
665
  ignore_on_conflict = run_args.get('ignore_on_conflict')
666
+ shallow = run_args.get('shallow', False)
667
+ depth = run_args.get('depth')
668
+ extra_git_args = run_args.get('extra_git_args')
607
669
 
608
670
  if sum(bool(var) for var in [branch, checkout, tag]) > 1:
609
671
  return {
@@ -617,7 +679,10 @@ class RepoAction(Action):
617
679
  pat,
618
680
  ssh,
619
681
  ignore_on_conflict=ignore_on_conflict,
620
- force=force)
682
+ force=force,
683
+ shallow=shallow,
684
+ depth=depth,
685
+ extra_git_args=extra_git_args)
621
686
  if res['return'] > 0:
622
687
  return res
623
688
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -26,4 +26,5 @@ mlcflow.egg-info/requires.txt
26
26
  mlcflow.egg-info/top_level.txt
27
27
  tests/test_action_invalid_meta_entries.py
28
28
  tests/test_cache_mark_tmp.py
29
+ tests/test_mlcflow_unix_installer.py
29
30
  tests/test_script_action_apptainer.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mlcflow"
7
- version = "1.2.5"
7
+ version = "1.2.6"
8
8
 
9
9
  description = "An automation interface tailored for CPU/GPU benchmarking"
10
10
  authors = [
@@ -0,0 +1,83 @@
1
+ import subprocess
2
+ import platform
3
+ import sys
4
+ from pathlib import Path
5
+
6
+
7
+ REPO_ROOT = Path(__file__).resolve().parents[1]
8
+ INSTALLER_SCRIPT = REPO_ROOT / "docs" / "install" / "mlcflow_unix_installer.sh"
9
+
10
+
11
+ def _resolve_venv_dir(tmp_path, setup_snippet):
12
+ default_dir = tmp_path / "mlcflow"
13
+ marker = "__RESULT__"
14
+ command = f"""
15
+ set -euo pipefail
16
+ source "{INSTALLER_SCRIPT}"
17
+ DEFAULT_VENV_DIR="{default_dir}"
18
+ VENV_DIR="$DEFAULT_VENV_DIR"
19
+ PY_MAJOR_MINOR="$(python3 -c 'import sys; print("{{}}.{{}}".format(*sys.version_info[:2]))')"
20
+ PY_ARCH="$(python3 -c 'import platform; print(platform.machine())')"
21
+ {setup_snippet}
22
+ resolve_default_venv_dir
23
+ echo "{marker}$VENV_DIR"
24
+ """
25
+ result = subprocess.run(
26
+ ["bash", "-lc", command],
27
+ text=True,
28
+ capture_output=True,
29
+ check=True,
30
+ )
31
+
32
+ for line in reversed(result.stdout.splitlines()):
33
+ if line.startswith(marker):
34
+ return line[len(marker):]
35
+
36
+ raise AssertionError(
37
+ f"Could not parse resolved venv path from output:\n{result.stdout}")
38
+
39
+
40
+ def test_resolve_default_venv_dir_prefers_compatible_default(tmp_path):
41
+ setup_snippet = """
42
+ python3 -m venv "$DEFAULT_VENV_DIR"
43
+ """
44
+ resolved = _resolve_venv_dir(tmp_path, setup_snippet)
45
+ assert resolved == str(tmp_path / "mlcflow")
46
+
47
+
48
+ def test_resolve_default_venv_dir_uses_suffix_for_incompatible_default(
49
+ tmp_path):
50
+ setup_snippet = """
51
+ mkdir -p "$DEFAULT_VENV_DIR"
52
+ """
53
+ resolved = _resolve_venv_dir(tmp_path, setup_snippet)
54
+ assert resolved.startswith(str(tmp_path / "mlcflow_"))
55
+ assert "_py" in resolved
56
+
57
+
58
+ def test_resolve_default_venv_dir_removes_stale_shared_env_when_default_incompatible(
59
+ tmp_path):
60
+ """When the default venv is incompatible and the suffixed venv already exists
61
+ but is itself stale/broken, the stale suffixed dir must be removed so that
62
+ setup_venv can create a fresh one rather than silently reusing a broken env."""
63
+ setup_snippet = """
64
+ mkdir -p "$DEFAULT_VENV_DIR"
65
+ SHARED_VENV_DIR="${DEFAULT_VENV_DIR}_${PY_ARCH}_py${PY_MAJOR_MINOR}"
66
+ mkdir -p "$SHARED_VENV_DIR"
67
+ """
68
+ resolved = _resolve_venv_dir(tmp_path, setup_snippet)
69
+ expected_suffix = f"{tmp_path / 'mlcflow'}_{platform.machine()}_py{sys.version_info[0]}.{sys.version_info[1]}"
70
+ assert resolved == expected_suffix
71
+ # The stale shared dir should have been removed so setup_venv creates it
72
+ # fresh
73
+ assert not Path(expected_suffix).exists()
74
+
75
+
76
+ def test_resolve_default_venv_dir_reuses_compatible_suffixed_env(tmp_path):
77
+ setup_snippet = """
78
+ SHARED_VENV_DIR="${DEFAULT_VENV_DIR}_${PY_ARCH}_py${PY_MAJOR_MINOR}"
79
+ python3 -m venv "$SHARED_VENV_DIR"
80
+ """
81
+ resolved = _resolve_venv_dir(tmp_path, setup_snippet)
82
+ expected = f"{tmp_path / 'mlcflow'}_{platform.machine()}_py{sys.version_info[0]}.{sys.version_info[1]}"
83
+ assert resolved == expected
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