Skip to content

Commit 2c2aba2

Browse files
committed
utility to setup env and model package dependencies
1 parent b539f0a commit 2c2aba2

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

api/__init__.py

Whitespace-only changes.

api/setup_env.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import os
2+
import re
3+
import subprocess
4+
import sys
5+
from pathlib import Path
6+
7+
# Conditional import of tomllib based on Python version
8+
if sys.version_info >= (3, 11):
9+
import tomllib # built-in in Python 3.11+
10+
else:
11+
import toml as tomllib # use third-party toml library for older versions
12+
13+
14+
class SetupEnvAndPackage:
15+
"""Utility class for cloning a repository, setting up a virtual environment, and installing a package."""
16+
17+
def setup(
18+
self,
19+
repo_url: str,
20+
clone_dir: Path,
21+
venv_dir: Path,
22+
venv_name: str = ".venv-chebifier",
23+
) -> None:
24+
"""
25+
Orchestrates the full setup process: cloning the repository,
26+
creating a virtual environment, and installing the package.
27+
28+
Args:
29+
repo_url (str): URL of the Git repository.
30+
clone_dir (Path): Directory to clone the repo into.
31+
venv_dir (Path): Directory where the virtual environment will be created.
32+
venv_name (str): Name of the virtual environment folder.
33+
"""
34+
cloned_repo_path = self._clone_repo(repo_url, clone_dir)
35+
venv_path = self._create_virtualenv(venv_dir, venv_name)
36+
self._install_from_pyproject(venv_path, cloned_repo_path)
37+
38+
def _clone_repo(self, repo_url: str, clone_dir: Path) -> Path:
39+
"""
40+
Clone a Git repository into a specified directory.
41+
42+
Args:
43+
repo_url (str): Git URL to clone.
44+
clone_dir (Path): Directory to clone into.
45+
46+
Returns:
47+
Path: Path to the cloned repository.
48+
"""
49+
repo_name = repo_url.rstrip("/").split("/")[-1].replace(".git", "")
50+
clone_path = Path(clone_dir or repo_name)
51+
52+
if not clone_path.exists():
53+
print(f"Cloning {repo_url} into {clone_path}...")
54+
subprocess.check_call(
55+
["git", "clone", "--depth=1", repo_url, str(clone_path)]
56+
)
57+
else:
58+
print(f"Repo already exists at {clone_path}")
59+
60+
return clone_path
61+
62+
@staticmethod
63+
def _create_virtualenv(venv_dir: Path, venv_name: str = ".venv-chebifier") -> Path:
64+
"""
65+
Create a virtual environment at the specified path.
66+
67+
Args:
68+
venv_dir (Path): Base directory where the venv will be created.
69+
venv_name (str): Name of the virtual environment directory.
70+
71+
Returns:
72+
Path: Path to the virtual environment.
73+
"""
74+
venv_path = venv_dir / venv_name
75+
76+
if venv_path.exists():
77+
print(f"Virtual environment already exists at: {venv_path}")
78+
return venv_path
79+
80+
print(f"Creating virtual environment at: {venv_path}")
81+
82+
try:
83+
subprocess.check_call(["virtualenv", str(venv_path)])
84+
except FileNotFoundError:
85+
print("virtualenv not found, installing it now...")
86+
subprocess.check_call(
87+
[sys.executable, "-m", "pip", "install", "virtualenv"]
88+
)
89+
subprocess.check_call(["virtualenv", str(venv_path)])
90+
91+
return venv_path
92+
93+
def _install_from_pyproject(self, venv_dir: Path, cloned_repo_path: Path) -> None:
94+
"""
95+
Install the cloned package in editable mode.
96+
97+
Args:
98+
venv_dir (Path): Path to the virtual environment.
99+
cloned_repo_path (Path): Path to the cloned repository.
100+
"""
101+
pip_executable = (
102+
venv_dir / "Scripts" / "pip.exe"
103+
if os.name == "nt"
104+
else venv_dir / "bin" / "pip"
105+
)
106+
107+
if not pip_executable.exists():
108+
raise FileNotFoundError(f"pip not found at {pip_executable}")
109+
110+
try:
111+
package_name = self._get_package_name(cloned_repo_path)
112+
except Exception as e:
113+
raise RuntimeError(f"Error extracting package name: {e}")
114+
115+
try:
116+
subprocess.check_output(
117+
[str(pip_executable), "show", package_name], stderr=subprocess.DEVNULL
118+
)
119+
print(f"Package '{package_name}' is already installed.")
120+
except subprocess.CalledProcessError:
121+
print(f"Installing '{package_name}' from {cloned_repo_path}...")
122+
subprocess.check_call(
123+
[str(pip_executable), "install", "-e", "."],
124+
cwd=cloned_repo_path,
125+
)
126+
127+
@staticmethod
128+
def _get_package_name(cloned_repo_path: Path) -> str:
129+
"""
130+
Extracts the package name from `pyproject.toml` or `setup.py`.
131+
132+
Args:
133+
cloned_repo_path (Path): Path to the cloned repository.
134+
135+
Returns:
136+
str: Name of the Python package.
137+
138+
Raises:
139+
ValueError: If parsing fails.
140+
FileNotFoundError: If neither config file is found.
141+
"""
142+
pyproject_path = cloned_repo_path / "pyproject.toml"
143+
setup_path = cloned_repo_path / "setup.py"
144+
145+
if pyproject_path.exists():
146+
try:
147+
with pyproject_path.open("rb") as f:
148+
pyproject = tomllib.load(f)
149+
return pyproject["project"]["name"]
150+
except Exception as e:
151+
raise ValueError(f"Failed to parse pyproject.toml: {e}")
152+
153+
elif setup_path.exists():
154+
try:
155+
setup_contents = setup_path.read_text()
156+
match = re.search(r'name\s*=\s*[\'"]([^\'"]+)[\'"]', setup_contents)
157+
if match:
158+
return match.group(1)
159+
else:
160+
raise ValueError("Could not find package name in setup.py")
161+
except Exception as e:
162+
raise ValueError(f"Failed to parse setup.py: {e}")
163+
164+
else:
165+
raise FileNotFoundError("Neither pyproject.toml nor setup.py found.")

0 commit comments

Comments
 (0)