Skip to content

Commit 08e30bb

Browse files
committed
feat: Enhance PyEnvManager with improved environment handling
- Refactor environment creation and dependency management - Add support for updating existing environments - Implement separate removal methods for different env types
1 parent 5705c73 commit 08e30bb

File tree

1 file changed

+88
-42
lines changed

1 file changed

+88
-42
lines changed

devchat/workflow/env_manager.py

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import hashlib
22
import os
3+
import shutil
34
import subprocess
45
import sys
56
from typing import Dict, Optional, Tuple
@@ -82,6 +83,7 @@ def ensure(
8283
py = self.get_py(env_name)
8384

8485
should_remove_old = False
86+
should_install_deps = False
8587

8688
if py:
8789
# check the version of the python executable
@@ -91,60 +93,60 @@ def ensure(
9193
should_remove_old = True
9294

9395
if reqirements_file and self.should_reinstall(env_name, reqirements_file):
94-
should_remove_old = True
96+
should_install_deps = True
9597

96-
if not should_remove_old:
98+
if not should_remove_old and not should_install_deps:
9799
return py
98100

99101
log_file = get_logging_file()
100102
print("\n```Step\n# Setting up workflow environment\n", flush=True)
101103
if should_remove_old:
102-
print(f"- Dependencies of {env_name} have been changed.", flush=True)
104+
print(f"- Python version of {env_name} needs to be updated.", flush=True)
103105
print(f"- Removing the old {env_name}...", flush=True)
104-
self.remove(env_name)
106+
self.remove(env_name, py_version)
107+
108+
# create the environment if it doesn't exist or needs to be recreated
109+
if should_remove_old or not py:
110+
if py_version:
111+
print(f"- Creating {env_name} with {py_version}...", flush=True)
112+
create_ok, msg = self.create(env_name, py_version)
113+
else:
114+
print(f"- Creating {env_name} with current Python version...", flush=True)
115+
create_ok, msg = self.create_with_virtualenv(env_name)
116+
117+
if not create_ok:
118+
print(f"- Failed to create {env_name}.", flush=True)
119+
print(f"\nFor more details, check {log_file}.", flush=True)
120+
print("\n```", flush=True)
121+
print(
122+
f"\n\nFailed to create {env_name}, the workflow will not run.",
123+
flush=True,
124+
)
125+
logger.warning(f"Failed to create {env_name}: {msg}")
126+
sys.exit(0)
105127

106-
# create the environment
107-
if py_version:
108-
print(f"- Creating {env_name} with {py_version}...", flush=True)
109-
create_ok, msg = self.create(env_name, py_version)
110-
else:
111-
print(f"- Creating {env_name} with current Python version...", flush=True)
112-
create_ok, msg = self.create_with_virtualenv(env_name)
113-
114-
if not create_ok:
115-
print(f"- Failed to create {env_name}.", flush=True)
116-
print(f"\nFor more details, check {log_file}.", flush=True)
117-
print("\n```", flush=True)
118-
print(
119-
f"\n\nFailed to create {env_name}, the workflow will not run.",
120-
flush=True,
121-
)
122-
logger.warning(f"Failed to create {env_name}: {msg}")
123-
sys.exit(0)
124-
# return None
125-
126-
# install the requirements
128+
# install or update the requirements
127129
if reqirements_file:
128130
filename = os.path.basename(reqirements_file)
129-
print(f"- Installing dependencies from {filename}...", flush=True)
131+
action = "Updating" if should_install_deps else "Installing"
132+
print(f"- {action} dependencies from {filename}...", flush=True)
130133
install_ok, msg = self.install(env_name, reqirements_file)
131134
if not install_ok:
132-
print(f"- Failed to install dependencies from {filename}.", flush=True)
135+
print(f"- Failed to {action.lower()} dependencies from {filename}.", flush=True)
133136
print(f"\nFor more details, check {log_file}.", flush=True)
134137
print("\n```", flush=True)
135138
print(
136-
"\n\nFailed to install dependencies, the workflow will not run.",
139+
f"\n\nFailed to {action.lower()} dependencies, the workflow may not run correctly.",
137140
flush=True,
138141
)
139-
logger.warning(f"Failed to install dependencies: {msg}")
142+
logger.warning(f"Failed to {action.lower()} dependencies: {msg}")
140143
sys.exit(0)
141-
# return None
142-
143-
# save the hash of the requirements file content
144-
dep_hash = self.get_dep_hash(reqirements_file)
145-
cache_file = os.path.join(ENV_CACHE_DIR, f"{env_name}")
146-
with open(cache_file, "w", encoding="utf-8") as f:
147-
f.write(dep_hash)
144+
else:
145+
# save the hash of the requirements file content
146+
dep_hash = self.get_dep_hash(reqirements_file)
147+
cache_file = os.path.join(ENV_CACHE_DIR, f"{env_name}")
148+
with open(cache_file, "w", encoding="utf-8") as f:
149+
f.write(dep_hash)
148150

149151
print("\n```", flush=True)
150152
return self.get_py(env_name)
@@ -160,16 +162,31 @@ def create_with_virtualenv(self, env_name: str) -> Tuple[bool, str]:
160162
try:
161163
# Use virtualenv.cli_run to create a virtual environment
162164
virtualenv.cli_run([env_path, "--python", sys.executable])
165+
166+
# Create sitecustomize.py in the lib/site-packages directory
167+
site_packages_dir = os.path.join(env_path, "Lib", "site-packages")
168+
if os.path.exists(site_packages_dir):
169+
sitecustomize_path = os.path.join(site_packages_dir, "sitecustomize.py")
170+
with open(sitecustomize_path, "w") as f:
171+
f.write("import sys\n")
172+
f.write("sys.path = [path for path in sys.path if path.find(\"conda\") == -1]")
173+
163174
return True, ""
164175
except Exception as e:
165176
return False, str(e)
166177

167178
def install(self, env_name: str, requirements_file: str) -> Tuple[bool, str]:
168179
"""
169-
Install requirements into the python environment.
180+
Install or update requirements in the python environment.
170181
171182
Args:
172-
requirements: the absolute path to the requirements file.
183+
env_name: the name of the python environment
184+
requirements_file: the absolute path to the requirements file.
185+
186+
Returns:
187+
A tuple (success, message), where success is a boolean indicating
188+
whether the installation was successful, and message is a string
189+
containing output or error information.
173190
"""
174191
py = self.get_py(env_name)
175192
if not py:
@@ -178,6 +195,7 @@ def install(self, env_name: str, requirements_file: str) -> Tuple[bool, str]:
178195
if not os.path.exists(requirements_file):
179196
return False, "Dependencies file not found."
180197

198+
# Base command
181199
cmd = [
182200
py,
183201
"-m",
@@ -189,17 +207,24 @@ def install(self, env_name: str, requirements_file: str) -> Tuple[bool, str]:
189207
PYPI_TUNA,
190208
"--no-warn-script-location",
191209
]
210+
211+
# Check if this is an update or a fresh install
212+
cache_file = os.path.join(ENV_CACHE_DIR, f"{env_name}")
213+
if os.path.exists(cache_file):
214+
# This is an update, add --upgrade flag
215+
cmd.append("--upgrade")
216+
192217
env = os.environ.copy()
193-
env.pop("PYTHONPATH")
218+
env.pop("PYTHONPATH", None)
194219
with subprocess.Popen(
195220
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, env=env
196221
) as proc:
197222
_, err = proc.communicate()
198223

199224
if proc.returncode != 0:
200-
return False, err.decode("utf-8")
225+
return False, f"Installation failed: {err.decode('utf-8')}"
201226

202-
return True, ""
227+
return True, f"Installation successful"
203228

204229
def should_reinstall(self, env_name: str, requirements_file: str) -> bool:
205230
"""
@@ -247,7 +272,28 @@ def create(self, env_name: str, py_version: str) -> Tuple[bool, str]:
247272
return False, msg
248273
return True, ""
249274

250-
def remove(self, env_name: str) -> bool:
275+
def remove(self, env_name: str, py_version: Optional[str] = None) -> bool:
276+
if py_version:
277+
return self.remove_by_mamba(env_name)
278+
return self.remove_by_del(env_name)
279+
280+
def remove_by_del(self, env_name: str) -> bool:
281+
"""
282+
Remove the python environment.
283+
"""
284+
env_path = os.path.join(MAMBA_PY_ENVS, env_name)
285+
try:
286+
# Remove the environment directory
287+
if os.path.exists(env_path):
288+
shutil.rmtree(env_path)
289+
return True
290+
except Exception as e:
291+
print(f"Failed to remove environment {env_name}: {e}")
292+
return False
293+
294+
295+
296+
def remove_by_mamba(self, env_name: str) -> bool:
251297
"""
252298
Remove the python environment.
253299
"""

0 commit comments

Comments
 (0)