What You’ll Learn
- How to upload a multi-file Python project into a sandbox to simulate a repository
- How to run a test suite before and after a fix to demonstrate a regression cycle
- How to apply a targeted code fix by reading, patching, and writing a file
- How to show a simple diff of the change inside the sandbox
Prerequisites
- Declaw running locally or in the cloud (see Deployment)
DECLAW_API_KEY and DECLAW_DOMAIN set in your environment
This example is available in Python. TypeScript support coming soon.
Code Walkthrough
1. The buggy repository
The simulated repository contains two modules and a test file. string_utils.py has a deliberate bug — the count_vowels function is missing 'u' from the vowel set:
STRING_UTILS = """\
def count_vowels(s: str) -> int:
\"\"\"Count the number of vowels in a string.\"\"\"
# BUG: missing 'u' from vowels
return sum(1 for c in s.lower() if c in "aeio")
"""
MATH_UTILS = """\
def factorial(n: int) -> int:
if n < 0:
raise ValueError("n must be non-negative")
if n <= 1:
return 1
return n * factorial(n - 1)
def fibonacci(n: int) -> list:
if n <= 0: return []
if n == 1: return [0]
seq = [0, 1]
while len(seq) < n:
seq.append(seq[-1] + seq[-2])
return seq
"""
The test file has cases that will fail due to the bug:
TEST_FILE = """\
class TestStringUtils(unittest.TestCase):
def test_count_vowels(self):
self.assertEqual(count_vowels("hello"), 2)
self.assertEqual(count_vowels("AEIOU"), 5) # Will fail: counts 4, not 5
self.assertEqual(count_vowels("ubuntu"), 3) # Will fail: counts 2, not 3
"""
2. Upload and run tests before the fix
from declaw import Sandbox
sbx = Sandbox.create(template="python", timeout=300)
try:
sbx.files.write("/home/user/repo/string_utils.py", STRING_UTILS)
sbx.files.write("/home/user/repo/math_utils.py", MATH_UTILS)
sbx.files.write("/home/user/repo/test_all.py", TEST_FILE)
result_before = sbx.commands.run(
"cd /home/user/repo && python3 -m unittest test_all -v 2>&1"
)
print(result_before.stdout)
print(f"Exit code (before): {result_before.exit_code}") # 1 (failure)
3. Apply the bug fix
The fix script reads the file, applies a targeted string replacement, and writes it back:
FIX_SCRIPT = """\
with open("/home/user/repo/string_utils.py", "r") as f:
content = f.read()
# Save original for diff
with open("/home/user/repo/string_utils.py.orig", "w") as f:
f.write(content)
# Apply the fix: add missing 'u' to vowels string
fixed = content.replace(
'return sum(1 for c in s.lower() if c in "aeio")',
'return sum(1 for c in s.lower() if c in "aeiou")'
)
with open("/home/user/repo/string_utils.py", "w") as f:
f.write(fixed)
print("Fix applied: added 'u' to vowels in count_vowels()")
"""
sbx.files.write("/home/user/repo/fix.py", FIX_SCRIPT)
sbx.commands.run("python3 /home/user/repo/fix.py")
4. Show the diff
DIFF_SCRIPT = """\
with open("/home/user/repo/string_utils.py.orig") as f:
original_lines = f.readlines()
with open("/home/user/repo/string_utils.py") as f:
fixed_lines = f.readlines()
for i, (orig, fixed) in enumerate(zip(original_lines, fixed_lines), 1):
if orig != fixed:
print(f"Line {i}:")
print(f" - {orig.rstrip()}")
print(f" + {fixed.rstrip()}")
"""
sbx.files.write("/home/user/repo/diff.py", DIFF_SCRIPT)
diff_result = sbx.commands.run("python3 /home/user/repo/diff.py")
print(diff_result.stdout)
5. Run tests after the fix
result_after = sbx.commands.run(
"cd /home/user/repo && python3 -m unittest test_all -v 2>&1"
)
print(result_after.stdout)
print(f"Exit code (after): {result_after.exit_code}") # 0 (all pass)
before_status = "PASS" if result_before.exit_code == 0 else "FAIL"
after_status = "PASS" if result_after.exit_code == 0 else "FAIL"
print(f"Before fix: {before_status}")
print(f"After fix: {after_status}")
finally:
sbx.kill()
Expected Output
--- Running Tests (BEFORE fix) ---
test_capitalize_words ... ok
test_count_vowels ... FAIL
test_is_palindrome ... ok
test_reverse_string ... ok
test_factorial ... ok
test_fibonacci ... ok
test_gcd ... ok
FAILED (failures=1)
Exit code: 1
--- Applying Bug Fix ---
Fix applied: added 'u' to vowels in count_vowels()
--- Diff (before vs after) ---
--- string_utils.py (before)
+++ string_utils.py (after)
Line 10:
- return sum(1 for c in s.lower() if c in "aeio")
+ return sum(1 for c in s.lower() if c in "aeiou")
--- Running Tests (AFTER fix) ---
test_capitalize_words ... ok
test_count_vowels ... ok
test_is_palindrome ... ok
test_reverse_string ... ok
...
Ran 7 tests in 0.001s
OK
Exit code: 0
--- Summary ---
Before fix: FAIL (exit code 1)
After fix: PASS (exit code 0)
Extending to Real Git Repositories
The example simulates a repository by uploading files. To work with a real git repository, install git first and then clone:
sbx = Sandbox.create(
template="python",
timeout=600,
network={"allow_out": ["github.com"]}, # Allow GitHub access
)
sbx.commands.run("apt-get install -y git 2>&1")
sbx.commands.run(
"git clone https://github.com/your-org/your-repo /home/user/repo 2>&1"
)
When cloning a public repository, set network={"allow_out": ["github.com"]} so the sandbox can reach GitHub but nothing else. For private repositories, pass a personal access token as an environment variable and restrict the allow-list to github.com only.