yanex 0.1.0__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.
- yanex/__init__.py +74 -0
- yanex/api.py +507 -0
- yanex/cli/__init__.py +3 -0
- yanex/cli/_utils.py +114 -0
- yanex/cli/commands/__init__.py +3 -0
- yanex/cli/commands/archive.py +177 -0
- yanex/cli/commands/compare.py +320 -0
- yanex/cli/commands/confirm.py +198 -0
- yanex/cli/commands/delete.py +203 -0
- yanex/cli/commands/list.py +243 -0
- yanex/cli/commands/run.py +625 -0
- yanex/cli/commands/show.py +560 -0
- yanex/cli/commands/unarchive.py +177 -0
- yanex/cli/commands/update.py +282 -0
- yanex/cli/filters/__init__.py +8 -0
- yanex/cli/filters/base.py +286 -0
- yanex/cli/filters/time_utils.py +178 -0
- yanex/cli/formatters/__init__.py +7 -0
- yanex/cli/formatters/console.py +325 -0
- yanex/cli/main.py +45 -0
- yanex/core/__init__.py +3 -0
- yanex/core/comparison.py +549 -0
- yanex/core/config.py +587 -0
- yanex/core/constants.py +16 -0
- yanex/core/environment.py +146 -0
- yanex/core/git_utils.py +153 -0
- yanex/core/manager.py +555 -0
- yanex/core/storage.py +682 -0
- yanex/ui/__init__.py +1 -0
- yanex/ui/compare_table.py +524 -0
- yanex/utils/__init__.py +3 -0
- yanex/utils/exceptions.py +70 -0
- yanex/utils/validation.py +165 -0
- yanex-0.1.0.dist-info/METADATA +251 -0
- yanex-0.1.0.dist-info/RECORD +39 -0
- yanex-0.1.0.dist-info/WHEEL +5 -0
- yanex-0.1.0.dist-info/entry_points.txt +2 -0
- yanex-0.1.0.dist-info/licenses/LICENSE +21 -0
- yanex-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
"""
|
2
|
+
Environment capture utilities.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import platform
|
6
|
+
import sys
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any, Dict, Optional
|
9
|
+
|
10
|
+
import git
|
11
|
+
|
12
|
+
from ..utils.exceptions import GitError
|
13
|
+
from .git_utils import get_current_commit_info, get_git_repo, get_repository_info
|
14
|
+
|
15
|
+
|
16
|
+
def capture_python_environment() -> Dict[str, Any]:
|
17
|
+
"""
|
18
|
+
Capture Python environment information.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
Dictionary with Python environment details
|
22
|
+
"""
|
23
|
+
return {
|
24
|
+
"python_version": sys.version,
|
25
|
+
"python_version_info": {
|
26
|
+
"major": sys.version_info.major,
|
27
|
+
"minor": sys.version_info.minor,
|
28
|
+
"micro": sys.version_info.micro,
|
29
|
+
},
|
30
|
+
"python_executable": sys.executable,
|
31
|
+
"python_path": sys.path.copy(),
|
32
|
+
"platform": platform.platform(),
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
def capture_system_environment() -> Dict[str, Any]:
|
37
|
+
"""
|
38
|
+
Capture system environment information.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
Dictionary with system details
|
42
|
+
"""
|
43
|
+
return {
|
44
|
+
"platform": {
|
45
|
+
"system": platform.system(),
|
46
|
+
"release": platform.release(),
|
47
|
+
"version": platform.version(),
|
48
|
+
"machine": platform.machine(),
|
49
|
+
"processor": platform.processor(),
|
50
|
+
"architecture": platform.architecture(),
|
51
|
+
},
|
52
|
+
"hostname": platform.node(),
|
53
|
+
"working_directory": str(Path.cwd()),
|
54
|
+
}
|
55
|
+
|
56
|
+
|
57
|
+
def capture_git_environment(repo_path: Optional[Path] = None) -> Dict[str, Any]:
|
58
|
+
"""
|
59
|
+
Capture git environment information.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
repo_path: Path to git repository, defaults to current directory
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Dictionary with git environment details
|
66
|
+
"""
|
67
|
+
try:
|
68
|
+
repo = get_git_repo(repo_path)
|
69
|
+
|
70
|
+
git_info = {
|
71
|
+
"repository": get_repository_info(repo),
|
72
|
+
"commit": get_current_commit_info(repo),
|
73
|
+
"git_version": git.Git().version(),
|
74
|
+
}
|
75
|
+
|
76
|
+
return git_info
|
77
|
+
|
78
|
+
except GitError:
|
79
|
+
# If we can't get git info, return minimal info
|
80
|
+
return {
|
81
|
+
"repository": None,
|
82
|
+
"commit": None,
|
83
|
+
"git_version": None,
|
84
|
+
"error": "Git repository not found or not accessible",
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
def capture_dependencies() -> Dict[str, Any]:
|
89
|
+
"""
|
90
|
+
Capture dependency information.
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
Dictionary with dependency details
|
94
|
+
"""
|
95
|
+
deps_info = {
|
96
|
+
"requirements_txt": None,
|
97
|
+
"environment_yml": None,
|
98
|
+
"pyproject_toml": None,
|
99
|
+
}
|
100
|
+
|
101
|
+
cwd = Path.cwd()
|
102
|
+
|
103
|
+
# Check for requirements.txt
|
104
|
+
requirements_path = cwd / "requirements.txt"
|
105
|
+
if requirements_path.exists():
|
106
|
+
try:
|
107
|
+
deps_info["requirements_txt"] = requirements_path.read_text().strip()
|
108
|
+
except Exception:
|
109
|
+
deps_info["requirements_txt"] = "Error reading requirements.txt"
|
110
|
+
|
111
|
+
# Check for environment.yml (conda)
|
112
|
+
env_yml_path = cwd / "environment.yml"
|
113
|
+
if env_yml_path.exists():
|
114
|
+
try:
|
115
|
+
deps_info["environment_yml"] = env_yml_path.read_text().strip()
|
116
|
+
except Exception:
|
117
|
+
deps_info["environment_yml"] = "Error reading environment.yml"
|
118
|
+
|
119
|
+
# Check for pyproject.toml
|
120
|
+
pyproject_path = cwd / "pyproject.toml"
|
121
|
+
if pyproject_path.exists():
|
122
|
+
try:
|
123
|
+
deps_info["pyproject_toml"] = pyproject_path.read_text().strip()
|
124
|
+
except Exception:
|
125
|
+
deps_info["pyproject_toml"] = "Error reading pyproject.toml"
|
126
|
+
|
127
|
+
return deps_info
|
128
|
+
|
129
|
+
|
130
|
+
def capture_full_environment(repo_path: Optional[Path] = None) -> Dict[str, Any]:
|
131
|
+
"""
|
132
|
+
Capture complete environment information.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
repo_path: Path to git repository, defaults to current directory
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
Dictionary with complete environment details
|
139
|
+
"""
|
140
|
+
return {
|
141
|
+
"python": capture_python_environment(),
|
142
|
+
"system": capture_system_environment(),
|
143
|
+
"git": capture_git_environment(repo_path),
|
144
|
+
"dependencies": capture_dependencies(),
|
145
|
+
"capture_timestamp": None, # Will be set by storage layer
|
146
|
+
}
|
yanex/core/git_utils.py
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
"""
|
2
|
+
Git integration utilities.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
import git
|
9
|
+
from git import Repo
|
10
|
+
|
11
|
+
from ..utils.exceptions import DirtyWorkingDirectoryError, GitError
|
12
|
+
|
13
|
+
|
14
|
+
def get_git_repo(path: Optional[Path] = None) -> Repo:
|
15
|
+
"""
|
16
|
+
Get git repository instance.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
path: Path to search for git repo, defaults to current directory
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Git repository instance
|
23
|
+
|
24
|
+
Raises:
|
25
|
+
GitError: If no git repository found
|
26
|
+
"""
|
27
|
+
if path is None:
|
28
|
+
path = Path.cwd()
|
29
|
+
|
30
|
+
try:
|
31
|
+
return Repo(path, search_parent_directories=True)
|
32
|
+
except git.InvalidGitRepositoryError as e:
|
33
|
+
raise GitError(f"No git repository found at {path}") from e
|
34
|
+
|
35
|
+
|
36
|
+
def validate_clean_working_directory(repo: Optional[Repo] = None) -> None:
|
37
|
+
"""
|
38
|
+
Validate that git working directory is clean.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
repo: Git repository instance, defaults to current directory
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
DirtyWorkingDirectoryError: If working directory has uncommitted changes
|
45
|
+
GitError: If git operations fail
|
46
|
+
"""
|
47
|
+
if repo is None:
|
48
|
+
repo = get_git_repo()
|
49
|
+
|
50
|
+
try:
|
51
|
+
if repo.is_dirty():
|
52
|
+
# Get list of changed files
|
53
|
+
changes = []
|
54
|
+
|
55
|
+
# Modified files
|
56
|
+
for item in repo.index.diff(None):
|
57
|
+
changes.append(f"Modified: {item.a_path}")
|
58
|
+
|
59
|
+
# Staged files
|
60
|
+
for item in repo.index.diff("HEAD"):
|
61
|
+
changes.append(f"Staged: {item.a_path}")
|
62
|
+
|
63
|
+
# Untracked files
|
64
|
+
for file_path in repo.untracked_files:
|
65
|
+
changes.append(f"Untracked: {file_path}")
|
66
|
+
|
67
|
+
raise DirtyWorkingDirectoryError(changes)
|
68
|
+
|
69
|
+
except git.GitError as e:
|
70
|
+
raise GitError(f"Git operation failed: {e}") from e
|
71
|
+
|
72
|
+
|
73
|
+
def get_current_commit_info(repo: Optional[Repo] = None) -> dict[str, str]:
|
74
|
+
"""
|
75
|
+
Get current git commit information.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
repo: Git repository instance, defaults to current directory
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
Dictionary with commit information
|
82
|
+
|
83
|
+
Raises:
|
84
|
+
GitError: If git operations fail
|
85
|
+
"""
|
86
|
+
if repo is None:
|
87
|
+
repo = get_git_repo()
|
88
|
+
|
89
|
+
try:
|
90
|
+
commit = repo.head.commit
|
91
|
+
|
92
|
+
return {
|
93
|
+
"commit_hash": commit.hexsha,
|
94
|
+
"commit_hash_short": commit.hexsha[:8],
|
95
|
+
"branch": repo.active_branch.name,
|
96
|
+
"author": str(commit.author),
|
97
|
+
"message": commit.message.strip(),
|
98
|
+
"committed_date": commit.committed_datetime.isoformat(),
|
99
|
+
}
|
100
|
+
|
101
|
+
except git.GitError as e:
|
102
|
+
raise GitError(f"Failed to get commit info: {e}") from e
|
103
|
+
|
104
|
+
|
105
|
+
def get_repository_info(repo: Optional[Repo] = None) -> dict[str, str]:
|
106
|
+
"""
|
107
|
+
Get git repository information.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
repo: Git repository instance, defaults to current directory
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
Dictionary with repository information
|
114
|
+
|
115
|
+
Raises:
|
116
|
+
GitError: If git operations fail
|
117
|
+
"""
|
118
|
+
if repo is None:
|
119
|
+
repo = get_git_repo()
|
120
|
+
|
121
|
+
try:
|
122
|
+
info = {
|
123
|
+
"repo_path": str(repo.working_dir),
|
124
|
+
"git_dir": str(repo.git_dir),
|
125
|
+
}
|
126
|
+
|
127
|
+
# Get remote URL if available
|
128
|
+
try:
|
129
|
+
if repo.remotes:
|
130
|
+
origin = repo.remotes.origin
|
131
|
+
info["remote_url"] = origin.url
|
132
|
+
else:
|
133
|
+
info["remote_url"] = None
|
134
|
+
except (AttributeError, IndexError):
|
135
|
+
info["remote_url"] = None
|
136
|
+
|
137
|
+
return info
|
138
|
+
|
139
|
+
except git.GitError as e:
|
140
|
+
raise GitError(f"Failed to get repository info: {e}") from e
|
141
|
+
|
142
|
+
|
143
|
+
def ensure_git_available() -> None:
|
144
|
+
"""
|
145
|
+
Ensure git is available in the system.
|
146
|
+
|
147
|
+
Raises:
|
148
|
+
GitError: If git command is not available
|
149
|
+
"""
|
150
|
+
try:
|
151
|
+
git.Git().version()
|
152
|
+
except git.GitCommandError as e:
|
153
|
+
raise GitError("Git command not found. Please install Git.") from e
|