Skip to content

Commit db5de7d

Browse files
authored
Merge pull request #4 from digitalocean/add-env-var-load-to-deploy
Add .env load to gradient deploy validation
2 parents a374b10 + 1b8b152 commit db5de7d

File tree

2 files changed

+312
-41
lines changed

2 files changed

+312
-41
lines changed

gradient_adk/cli/agent/deployment/validation.py

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Pre-deployment validation for agent entrypoints."""
2+
23
from __future__ import annotations
34
import shutil
45
import subprocess
@@ -10,42 +11,43 @@
1011

1112
class ValidationError(Exception):
1213
"""Raised when agent validation fails."""
14+
1315
pass
1416

1517

1618
def validate_agent_entrypoint(
17-
source_dir: Path,
18-
entrypoint_file: str,
19-
verbose: bool = False
19+
source_dir: Path, entrypoint_file: str, verbose: bool = False
2020
) -> None:
2121
"""
2222
Validate that the agent can run successfully in a fresh environment.
23-
23+
2424
This creates a temporary virtual environment, installs dependencies,
2525
and attempts to start the agent to verify it works before deployment.
26-
26+
2727
Args:
2828
source_dir: Directory containing the agent source code
2929
entrypoint_file: Relative path to the entrypoint file (e.g., "main.py")
3030
verbose: Whether to print verbose validation output
31-
31+
3232
Raises:
3333
ValidationError: If validation fails
3434
"""
35-
print(f"🔍 Validating agent can run before deployment... (skip this step with --skip-validation)")
35+
print(
36+
f"🔍 Validating agent can run before deployment... (skip this step with --skip-validation)"
37+
)
3638
if verbose:
3739
print(f"🔍 Validating agent before deployment...")
3840
print(f" Source: {source_dir}")
3941
print(f" Entrypoint: {entrypoint_file}")
40-
42+
4143
# Check file exists
4244
entrypoint_path = source_dir / entrypoint_file
4345
if not entrypoint_path.exists():
4446
raise ValidationError(
4547
f"Entrypoint file not found: {entrypoint_file}\n"
4648
f"Expected at: {entrypoint_path}"
4749
)
48-
50+
4951
# Check if requirements.txt exists
5052
requirements_path = source_dir / "requirements.txt"
5153
if not requirements_path.exists():
@@ -55,86 +57,98 @@ def validate_agent_entrypoint(
5557
f"Create a requirements.txt with at minimum:\n"
5658
f" gradient-adk\n"
5759
)
58-
60+
5961
# Check for config file
6062
config_path = source_dir / ".gradient" / "agent.yml"
6163
if not config_path.exists():
6264
raise ValidationError(
6365
f"No agent configuration found at {config_path}\n"
6466
f"Run 'gradient agent configure' first to set up your agent."
6567
)
66-
68+
6769
# Create temporary directory for validation
6870
temp_dir = None
6971
try:
7072
temp_dir = Path(tempfile.mkdtemp(prefix="gradient_validation_"))
71-
73+
7274
if verbose:
7375
print(f"\n� Created temporary validation environment: {temp_dir}")
74-
76+
7577
# Copy source files to temp directory
7678
if verbose:
7779
print(f"📋 Copying source files...")
78-
80+
7981
# Copy all files except common exclusions
8082
_copy_source_files(source_dir, temp_dir, verbose=verbose)
81-
83+
8284
# Create virtual environment
8385
venv_path = temp_dir / ".venv"
8486
if verbose:
8587
print(f"\n🔨 Creating virtual environment...")
86-
88+
8789
result = subprocess.run(
8890
[sys.executable, "-m", "venv", str(venv_path)],
8991
capture_output=True,
9092
text=True,
91-
timeout=60
93+
timeout=60,
9294
)
93-
95+
9496
if result.returncode != 0:
9597
raise ValidationError(
9698
f"Failed to create virtual environment:\n{result.stderr}"
9799
)
98-
100+
99101
# Get pip path
100102
if sys.platform == "win32":
101103
pip_path = venv_path / "Scripts" / "pip"
102104
python_path = venv_path / "Scripts" / "python"
103105
else:
104106
pip_path = venv_path / "bin" / "pip"
105107
python_path = venv_path / "bin" / "python"
106-
108+
107109
# Install requirements
108110
if verbose:
109111
print(f"📦 Installing dependencies from requirements.txt...")
110-
112+
111113
result = subprocess.run(
112114
[str(pip_path), "install", "-r", "requirements.txt"],
113115
cwd=temp_dir,
114116
capture_output=True,
115117
text=True,
116-
timeout=300 # 5 minutes for dependency installation
118+
timeout=300, # 5 minutes for dependency installation
117119
)
118-
120+
119121
if result.returncode != 0:
120122
raise ValidationError(
121123
f"Failed to install dependencies:\n{result.stderr}\n\n"
122124
f"Fix your requirements.txt and try again."
123125
)
124-
126+
125127
if verbose:
126128
print(f"✅ Dependencies installed successfully")
127-
129+
128130
# Try to import the entrypoint and verify decorator
129131
if verbose:
130132
print(f"\n🔍 Verifying agent entrypoint has @entrypoint decorator...")
131-
133+
132134
# Check that the entrypoint file has the @entrypoint decorator
135+
# Convert file path to module path (e.g., "agents/my_agent.py" -> "agents.my_agent")
136+
module_path = entrypoint_file.replace(".py", "").replace("/", ".")
137+
133138
check_script = f"""
134139
import sys
135140
import re
141+
import os
136142
sys.path.insert(0, '.')
137143
144+
# Load environment variables from .env file if it exists
145+
if os.path.exists('.env'):
146+
try:
147+
from dotenv import load_dotenv
148+
load_dotenv()
149+
except ImportError:
150+
pass
151+
138152
# Read the entrypoint file
139153
with open('{entrypoint_file}', 'r') as f:
140154
content = f.read()
@@ -146,21 +160,23 @@ def validate_agent_entrypoint(
146160
147161
# Try to import it
148162
try:
149-
from {entrypoint_file.replace('.py', '')} import *
163+
import {module_path}
150164
print('✅ Entrypoint imported successfully with @entrypoint decorator')
151165
except Exception as e:
152166
print(f"ERROR: {{type(e).__name__}}: {{e}}")
167+
import traceback
168+
traceback.print_exc()
153169
sys.exit(1)
154170
"""
155-
171+
156172
result = subprocess.run(
157173
[str(python_path), "-c", check_script],
158174
cwd=temp_dir,
159175
capture_output=True,
160176
text=True,
161-
timeout=30
177+
timeout=30,
162178
)
163-
179+
164180
if result.returncode != 0:
165181
error_output = result.stdout + result.stderr
166182
if "No @entrypoint decorator found" in error_output:
@@ -178,13 +194,13 @@ def validate_agent_entrypoint(
178194
f"Failed to import entrypoint:\n{error_output}\n\n"
179195
f"The agent code has errors or missing dependencies."
180196
)
181-
197+
182198
if verbose:
183199
print(result.stdout.strip())
184-
200+
185201
if verbose:
186202
print(f"\n✅ Agent validation passed - ready to deploy!")
187-
203+
188204
except subprocess.TimeoutExpired:
189205
raise ValidationError(
190206
"Validation timed out - the agent may have infinite loops or be waiting for input.\n"
@@ -212,7 +228,7 @@ def validate_agent_entrypoint(
212228

213229
def _copy_source_files(source_dir: Path, dest_dir: Path, verbose: bool = False) -> None:
214230
"""Copy source files to destination, excluding common patterns."""
215-
231+
216232
# Common exclusions
217233
exclude_patterns = {
218234
"__pycache__",
@@ -221,15 +237,14 @@ def _copy_source_files(source_dir: Path, dest_dir: Path, verbose: bool = False)
221237
".venv",
222238
"venv",
223239
"env",
224-
".env",
225240
"node_modules",
226241
".pytest_cache",
227242
".mypy_cache",
228243
"*.egg-info",
229244
"dist",
230245
"build",
231246
}
232-
247+
233248
def should_exclude(path: Path) -> bool:
234249
"""Check if a path should be excluded."""
235250
for pattern in exclude_patterns:
@@ -239,22 +254,28 @@ def should_exclude(path: Path) -> bool:
239254
elif path.name == pattern:
240255
return True
241256
return False
242-
257+
243258
# Copy files and directories
244259
for item in source_dir.iterdir():
245260
if should_exclude(item):
246261
if verbose:
247262
print(f" Skipping: {item.name}")
248263
continue
249-
264+
250265
dest_path = dest_dir / item.name
251-
266+
252267
try:
253268
if item.is_dir():
254-
shutil.copytree(item, dest_path, ignore=lambda d, files: [f for f in files if should_exclude(Path(d) / f)])
269+
shutil.copytree(
270+
item,
271+
dest_path,
272+
ignore=lambda d, files: [
273+
f for f in files if should_exclude(Path(d) / f)
274+
],
275+
)
255276
else:
256277
shutil.copy2(item, dest_path)
257-
278+
258279
if verbose:
259280
print(f" Copied: {item.name}")
260281
except Exception as e:

0 commit comments

Comments
 (0)