expops 0.1.3__py3-none-any.whl
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.
- expops-0.1.3.dist-info/METADATA +826 -0
- expops-0.1.3.dist-info/RECORD +86 -0
- expops-0.1.3.dist-info/WHEEL +5 -0
- expops-0.1.3.dist-info/entry_points.txt +3 -0
- expops-0.1.3.dist-info/licenses/LICENSE +674 -0
- expops-0.1.3.dist-info/top_level.txt +1 -0
- mlops/__init__.py +0 -0
- mlops/__main__.py +11 -0
- mlops/_version.py +34 -0
- mlops/adapters/__init__.py +12 -0
- mlops/adapters/base.py +86 -0
- mlops/adapters/config_schema.py +89 -0
- mlops/adapters/custom/__init__.py +3 -0
- mlops/adapters/custom/custom_adapter.py +447 -0
- mlops/adapters/plugin_manager.py +113 -0
- mlops/adapters/sklearn/__init__.py +3 -0
- mlops/adapters/sklearn/adapter.py +94 -0
- mlops/cluster/__init__.py +3 -0
- mlops/cluster/controller.py +496 -0
- mlops/cluster/process_runner.py +91 -0
- mlops/cluster/providers.py +258 -0
- mlops/core/__init__.py +95 -0
- mlops/core/custom_model_base.py +38 -0
- mlops/core/dask_networkx_executor.py +1265 -0
- mlops/core/executor_worker.py +1239 -0
- mlops/core/experiment_tracker.py +81 -0
- mlops/core/graph_types.py +64 -0
- mlops/core/networkx_parser.py +135 -0
- mlops/core/payload_spill.py +278 -0
- mlops/core/pipeline_utils.py +162 -0
- mlops/core/process_hashing.py +216 -0
- mlops/core/step_state_manager.py +1298 -0
- mlops/core/step_system.py +956 -0
- mlops/core/workspace.py +99 -0
- mlops/environment/__init__.py +10 -0
- mlops/environment/base.py +43 -0
- mlops/environment/conda_manager.py +307 -0
- mlops/environment/factory.py +70 -0
- mlops/environment/pyenv_manager.py +146 -0
- mlops/environment/setup_env.py +31 -0
- mlops/environment/system_manager.py +66 -0
- mlops/environment/utils.py +105 -0
- mlops/environment/venv_manager.py +134 -0
- mlops/main.py +527 -0
- mlops/managers/project_manager.py +400 -0
- mlops/managers/reproducibility_manager.py +575 -0
- mlops/platform.py +996 -0
- mlops/reporting/__init__.py +16 -0
- mlops/reporting/context.py +187 -0
- mlops/reporting/entrypoint.py +292 -0
- mlops/reporting/kv_utils.py +77 -0
- mlops/reporting/registry.py +50 -0
- mlops/runtime/__init__.py +9 -0
- mlops/runtime/context.py +34 -0
- mlops/runtime/env_export.py +113 -0
- mlops/storage/__init__.py +12 -0
- mlops/storage/adapters/__init__.py +9 -0
- mlops/storage/adapters/gcp_kv_store.py +778 -0
- mlops/storage/adapters/gcs_object_store.py +96 -0
- mlops/storage/adapters/memory_store.py +240 -0
- mlops/storage/adapters/redis_store.py +438 -0
- mlops/storage/factory.py +199 -0
- mlops/storage/interfaces/__init__.py +6 -0
- mlops/storage/interfaces/kv_store.py +118 -0
- mlops/storage/path_utils.py +38 -0
- mlops/templates/premier-league/charts/plot_metrics.js +70 -0
- mlops/templates/premier-league/charts/plot_metrics.py +145 -0
- mlops/templates/premier-league/charts/requirements.txt +6 -0
- mlops/templates/premier-league/configs/cluster_config.yaml +13 -0
- mlops/templates/premier-league/configs/project_config.yaml +207 -0
- mlops/templates/premier-league/data/England CSV.csv +12154 -0
- mlops/templates/premier-league/models/premier_league_model.py +638 -0
- mlops/templates/premier-league/requirements.txt +8 -0
- mlops/templates/sklearn-basic/README.md +22 -0
- mlops/templates/sklearn-basic/charts/plot_metrics.py +85 -0
- mlops/templates/sklearn-basic/charts/requirements.txt +3 -0
- mlops/templates/sklearn-basic/configs/project_config.yaml +64 -0
- mlops/templates/sklearn-basic/data/train.csv +14 -0
- mlops/templates/sklearn-basic/models/model.py +62 -0
- mlops/templates/sklearn-basic/requirements.txt +10 -0
- mlops/web/__init__.py +3 -0
- mlops/web/server.py +585 -0
- mlops/web/ui/index.html +52 -0
- mlops/web/ui/mlops-charts.js +357 -0
- mlops/web/ui/script.js +1244 -0
- mlops/web/ui/styles.css +248 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_args() -> argparse.Namespace:
|
|
10
|
+
p = argparse.ArgumentParser(description="Setup project environment and write interpreter path")
|
|
11
|
+
p.add_argument("--project-id", required=True)
|
|
12
|
+
p.add_argument("--project-dir", required=True, help="Path to repository root")
|
|
13
|
+
p.add_argument("--env-file", required=True, help="Path to write the interpreter path")
|
|
14
|
+
return p.parse_args()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
args = parse_args()
|
|
19
|
+
repo_root = Path(args.project_dir).resolve()
|
|
20
|
+
src_path = repo_root / "src"
|
|
21
|
+
if str(src_path) not in sys.path:
|
|
22
|
+
sys.path.insert(0, str(src_path))
|
|
23
|
+
|
|
24
|
+
from mlops.core.pipeline_utils import setup_environment_and_write_interpreter
|
|
25
|
+
|
|
26
|
+
py = setup_environment_and_write_interpreter(repo_root, args.project_id, args.env_file)
|
|
27
|
+
print(f"[env] Environment setup completed. Interpreter: {py}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .base import EnvironmentManager
|
|
8
|
+
from .utils import load_requirements, verify_pip_requirements
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SystemEnvironmentManager(EnvironmentManager):
|
|
12
|
+
"""System Python environment management (no virtual environment)."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config: dict[str, Any]):
|
|
15
|
+
super().__init__(config)
|
|
16
|
+
self.requirements = load_requirements(config)
|
|
17
|
+
self.environment_name = "system"
|
|
18
|
+
self.python_interpreter = sys.executable
|
|
19
|
+
|
|
20
|
+
def get_environment_type(self) -> str:
|
|
21
|
+
return "system"
|
|
22
|
+
|
|
23
|
+
def get_environment_name(self) -> str:
|
|
24
|
+
return self.environment_name
|
|
25
|
+
|
|
26
|
+
def get_python_interpreter(self) -> str:
|
|
27
|
+
return self.python_interpreter
|
|
28
|
+
|
|
29
|
+
def environment_exists(self) -> bool:
|
|
30
|
+
"""System environment always exists."""
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
def setup_environment(self) -> None:
|
|
34
|
+
"""Set up using the system Python environment."""
|
|
35
|
+
print(f"[SystemEnvironmentManager] Using system Python environment...")
|
|
36
|
+
|
|
37
|
+
if self.requirements:
|
|
38
|
+
print(f"[SystemEnvironmentManager] Installing requirements to system Python...")
|
|
39
|
+
try:
|
|
40
|
+
pip_install_cmd = [self.python_interpreter, "-m", "pip", "install"] + self.requirements
|
|
41
|
+
subprocess.run(pip_install_cmd, check=True)
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
raise RuntimeError(f"Failed to install requirements to system Python: {e}")
|
|
44
|
+
|
|
45
|
+
print(f"[SystemEnvironmentManager] Python interpreter: {self.python_interpreter}")
|
|
46
|
+
|
|
47
|
+
def verify_environment(self) -> bool:
|
|
48
|
+
"""Verify that the system environment is properly configured."""
|
|
49
|
+
print(f"[SystemEnvironmentManager] Verifying system Python environment...")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
python_version_output = subprocess.check_output([self.python_interpreter, '--version'], text=True, stderr=subprocess.STDOUT).strip()
|
|
53
|
+
print(f"[SystemEnvironmentManager] Python interpreter is working: {python_version_output}")
|
|
54
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
55
|
+
print(f"[SystemEnvironmentManager] Python interpreter '{self.python_interpreter}' is not working: {e}")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
if self.requirements:
|
|
59
|
+
ok, missing = verify_pip_requirements(self.python_interpreter, self.requirements)
|
|
60
|
+
if not ok:
|
|
61
|
+
print(f"[SystemEnvironmentManager] Missing packages: {', '.join(missing)}")
|
|
62
|
+
print(f"[SystemEnvironmentManager] Environment verification failed for system Python.")
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
print(f"[SystemEnvironmentManager] Environment verification successful for system Python.")
|
|
66
|
+
return True
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _read_requirement_lines(path: Path) -> list[str]:
|
|
10
|
+
lines: list[str] = []
|
|
11
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
12
|
+
for raw in f:
|
|
13
|
+
line = raw.strip()
|
|
14
|
+
if not line or line.startswith("#"):
|
|
15
|
+
continue
|
|
16
|
+
lines.append(line)
|
|
17
|
+
return lines
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_requirements(config: dict[str, Any]) -> list[str]:
|
|
21
|
+
"""Load pip-style requirements from a config dict.
|
|
22
|
+
|
|
23
|
+
Supported keys:
|
|
24
|
+
- `requirements`: list[str] or newline-separated str
|
|
25
|
+
- `requirements_file`: path to a requirements.txt file
|
|
26
|
+
"""
|
|
27
|
+
if "requirements" in config:
|
|
28
|
+
reqs = config.get("requirements")
|
|
29
|
+
if isinstance(reqs, str):
|
|
30
|
+
out: list[str] = []
|
|
31
|
+
for raw in reqs.splitlines():
|
|
32
|
+
line = raw.strip()
|
|
33
|
+
if not line or line.startswith("#"):
|
|
34
|
+
continue
|
|
35
|
+
out.append(line)
|
|
36
|
+
return out
|
|
37
|
+
if isinstance(reqs, list):
|
|
38
|
+
out: list[str] = []
|
|
39
|
+
for item in reqs:
|
|
40
|
+
s = str(item).strip()
|
|
41
|
+
if s and not s.startswith("#"):
|
|
42
|
+
out.append(s)
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
req_file = config.get("requirements_file")
|
|
46
|
+
if req_file:
|
|
47
|
+
path = Path(str(req_file))
|
|
48
|
+
if not path.exists():
|
|
49
|
+
raise FileNotFoundError(f"Requirements file not found: {path}")
|
|
50
|
+
return _read_requirement_lines(path)
|
|
51
|
+
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def requirement_to_package_name(requirement: str) -> str:
|
|
59
|
+
"""Best-effort extract a package name for `pip show` from a requirement spec."""
|
|
60
|
+
s = str(requirement).strip()
|
|
61
|
+
if not s:
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
# Handle "name @ url"
|
|
65
|
+
if "@" in s and not s.startswith("@"):
|
|
66
|
+
s = s.split("@", 1)[0].strip()
|
|
67
|
+
|
|
68
|
+
# Strip environment markers (PEP 508)
|
|
69
|
+
if ";" in s:
|
|
70
|
+
s = s.split(";", 1)[0].strip()
|
|
71
|
+
|
|
72
|
+
# Strip extras
|
|
73
|
+
if "[" in s:
|
|
74
|
+
s = s.split("[", 1)[0].strip()
|
|
75
|
+
|
|
76
|
+
# Strip version specifiers (common cases)
|
|
77
|
+
for op in ("===", "==", ">=", "<=", "!=", "~=", ">", "<", "="):
|
|
78
|
+
if op in s:
|
|
79
|
+
s = s.split(op, 1)[0].strip()
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
m = _NAME_RE.match(s)
|
|
83
|
+
return m.group(0) if m else s
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def verify_pip_requirements(python_exec: str, requirements: list[str]) -> tuple[bool, list[str]]:
|
|
87
|
+
"""Return (ok, missing_packages) using `pip show`."""
|
|
88
|
+
missing: list[str] = []
|
|
89
|
+
for req in requirements or []:
|
|
90
|
+
name = requirement_to_package_name(req)
|
|
91
|
+
if not name:
|
|
92
|
+
continue
|
|
93
|
+
try:
|
|
94
|
+
subprocess.check_output(
|
|
95
|
+
[python_exec, "-m", "pip", "show", name],
|
|
96
|
+
text=True,
|
|
97
|
+
stderr=subprocess.DEVNULL,
|
|
98
|
+
)
|
|
99
|
+
except subprocess.CalledProcessError:
|
|
100
|
+
missing.append(name)
|
|
101
|
+
except FileNotFoundError:
|
|
102
|
+
missing.append(name)
|
|
103
|
+
return (len(missing) == 0), missing
|
|
104
|
+
|
|
105
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .base import EnvironmentManager
|
|
10
|
+
from .utils import load_requirements, verify_pip_requirements
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VenvEnvironmentManager(EnvironmentManager):
|
|
14
|
+
"""Standard Python venv-based environment management."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: dict[str, Any]):
|
|
17
|
+
super().__init__(config)
|
|
18
|
+
self.requirements = load_requirements(config)
|
|
19
|
+
|
|
20
|
+
env_name = config.get("name")
|
|
21
|
+
if not env_name:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"Virtual environment name must be explicitly specified in the configuration. "
|
|
24
|
+
"Please add 'name' field under 'environment.venv' in your config file."
|
|
25
|
+
)
|
|
26
|
+
self.environment_name = env_name
|
|
27
|
+
# Prefer project-local `.venvs/` but preserve existing cache environments if present.
|
|
28
|
+
preferred_path = Path.cwd() / ".venvs" / self.environment_name
|
|
29
|
+
cache_path = Path.home() / ".cache" / "mlops-platform" / "venvs" / self.environment_name
|
|
30
|
+
|
|
31
|
+
if self._python_path(preferred_path).exists():
|
|
32
|
+
self.venv_path = preferred_path
|
|
33
|
+
elif self._python_path(cache_path).exists():
|
|
34
|
+
self.venv_path = cache_path
|
|
35
|
+
else:
|
|
36
|
+
self.venv_path = preferred_path if self._ensure_writable_dir(preferred_path.parent) else cache_path
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _python_path(venv_path: Path) -> Path:
|
|
40
|
+
if os.name == "nt":
|
|
41
|
+
return venv_path / "Scripts" / "python.exe"
|
|
42
|
+
return venv_path / "bin" / "python"
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _ensure_writable_dir(dir_path: Path) -> bool:
|
|
46
|
+
try:
|
|
47
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
probe = dir_path / ".mlops_write_probe"
|
|
49
|
+
probe.write_text("", encoding="utf-8")
|
|
50
|
+
probe.unlink(missing_ok=True)
|
|
51
|
+
return True
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def get_environment_type(self) -> str:
|
|
56
|
+
return "venv"
|
|
57
|
+
|
|
58
|
+
def get_environment_name(self) -> str:
|
|
59
|
+
return self.environment_name
|
|
60
|
+
|
|
61
|
+
def get_python_interpreter(self) -> str:
|
|
62
|
+
if not self.python_interpreter:
|
|
63
|
+
raise RuntimeError("Environment not set up yet. Call setup_environment() first.")
|
|
64
|
+
return self.python_interpreter
|
|
65
|
+
|
|
66
|
+
def environment_exists(self) -> bool:
|
|
67
|
+
"""Check if the virtual environment already exists."""
|
|
68
|
+
return self._python_path(self.venv_path).exists()
|
|
69
|
+
|
|
70
|
+
def setup_environment(self) -> None:
|
|
71
|
+
"""Set up the virtual environment based on configuration."""
|
|
72
|
+
print(f"[VenvEnvironmentManager] Starting venv environment setup for '{self.environment_name}'...")
|
|
73
|
+
|
|
74
|
+
if not self.environment_exists():
|
|
75
|
+
print(f"[VenvEnvironmentManager] Environment '{self.environment_name}' not found. Creating it...")
|
|
76
|
+
|
|
77
|
+
self.venv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
subprocess.run([sys.executable, "-m", "venv", str(self.venv_path)], check=True)
|
|
81
|
+
print(f"[VenvEnvironmentManager] Environment '{self.environment_name}' created successfully.")
|
|
82
|
+
except subprocess.CalledProcessError as e:
|
|
83
|
+
# One more fallback: use virtualenv if available
|
|
84
|
+
try:
|
|
85
|
+
subprocess.run([sys.executable, "-m", "virtualenv", str(self.venv_path)], check=True)
|
|
86
|
+
print(f"[VenvEnvironmentManager] Environment '{self.environment_name}' created via virtualenv.")
|
|
87
|
+
except Exception:
|
|
88
|
+
raise RuntimeError(f"Failed to create virtual environment '{self.environment_name}': {e}")
|
|
89
|
+
else:
|
|
90
|
+
print(f"[VenvEnvironmentManager] Using existing environment: '{self.environment_name}'")
|
|
91
|
+
|
|
92
|
+
self.python_interpreter = str(self._python_path(self.venv_path))
|
|
93
|
+
|
|
94
|
+
if not Path(self.python_interpreter).exists():
|
|
95
|
+
raise FileNotFoundError(f"Python interpreter not found in environment '{self.environment_name}' at expected path: {self.python_interpreter}")
|
|
96
|
+
|
|
97
|
+
if self.requirements:
|
|
98
|
+
print(f"[VenvEnvironmentManager] Installing requirements...")
|
|
99
|
+
try:
|
|
100
|
+
# Ensure modern build tooling to prefer wheels over source builds
|
|
101
|
+
subprocess.run([self.python_interpreter, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"], check=True)
|
|
102
|
+
pip_install_cmd = [self.python_interpreter, "-m", "pip", "install", "--no-cache-dir"] + self.requirements
|
|
103
|
+
subprocess.run(pip_install_cmd, check=True)
|
|
104
|
+
print(f"[VenvEnvironmentManager] Requirements installed successfully.")
|
|
105
|
+
except subprocess.CalledProcessError as e:
|
|
106
|
+
raise RuntimeError(f"Failed to install requirements: {e}")
|
|
107
|
+
|
|
108
|
+
print(f"[VenvEnvironmentManager] Python interpreter: {self.python_interpreter}")
|
|
109
|
+
print(f"[VenvEnvironmentManager] Environment setup completed for '{self.environment_name}'.")
|
|
110
|
+
|
|
111
|
+
def verify_environment(self) -> bool:
|
|
112
|
+
"""Verify that the environment is properly configured."""
|
|
113
|
+
if not self.python_interpreter:
|
|
114
|
+
print("[VenvEnvironmentManager] Python interpreter not set. Cannot verify environment.")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
print(f"[VenvEnvironmentManager] Verifying environment '{self.environment_name}'...")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
python_version_output = subprocess.check_output([self.python_interpreter, '--version'], text=True, stderr=subprocess.STDOUT).strip()
|
|
121
|
+
print(f"[VenvEnvironmentManager] Python interpreter is working: {python_version_output}")
|
|
122
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
123
|
+
print(f"[VenvEnvironmentManager] Python interpreter '{self.python_interpreter}' is not working: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
if self.requirements:
|
|
127
|
+
ok, missing = verify_pip_requirements(self.python_interpreter, self.requirements)
|
|
128
|
+
if not ok:
|
|
129
|
+
print(f"[VenvEnvironmentManager] Missing packages: {', '.join(missing)}")
|
|
130
|
+
print(f"[VenvEnvironmentManager] Environment verification failed for '{self.environment_name}'.")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
print(f"[VenvEnvironmentManager] Environment verification successful for '{self.environment_name}'.")
|
|
134
|
+
return True
|