* fix(windows): add pywin32 dependency and improve error handling (#627) Windows users were experiencing ModuleNotFoundError: No module named 'pywintypes' when running subtasks, because pywin32 is required by real_ladybug but was missing from requirements.txt. Changes: - apps/backend/requirements.txt: Add pywin32>=306 for Windows Python 3.12+ - apps/backend/core/dependency_validator.py: NEW - Validate platform-specific deps - apps/backend/cli/utils.py: Integrate dependency validation in validate_environment() - apps/backend/integrations/graphiti/queries_pkg/client.py: Improve Windows error logging - tests/test_github_pr_review.py: Fix deprecated asyncio.get_event_loop().run_until_complete() pattern, convert to async/await with @pytest.mark.asyncio Fixes #627 * fix: address PR feedback from code review - Use pathlib Path operator for proper Windows path separators - Use sys.prefix for venv path detection (works with conda, poetry, etc.) - Add hasattr check for ImportError.name for more robust pywin32 detection - Add Python version check (3.12+) to match requirements.txt constraint - Remove unnecessary pass statement Co-authored-by: gemini-code-assist[bot] <gemini-code-assist[bot]@users.noreply.github.com> * fix: correct misleading comment about conda support The comment incorrectly stated sys.prefix works for conda, but conda on Windows uses 'conda activate <env>' rather than Scripts/activate path. * chore: add config.json to .gitignore - Add /config.json to .gitignore to prevent accidental commits - Config files may contain sensitive settings and should not be tracked --------- Co-authored-by: gemini-code-assist[bot] <gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ Desktop.ini
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/config.json
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
|
||||
@@ -15,6 +15,7 @@ if str(_PARENT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_PARENT_DIR))
|
||||
|
||||
from core.auth import get_auth_token, get_auth_token_source
|
||||
from core.dependency_validator import validate_platform_dependencies
|
||||
|
||||
|
||||
def import_dotenv():
|
||||
@@ -154,6 +155,9 @@ def validate_environment(spec_dir: Path) -> bool:
|
||||
Returns:
|
||||
True if valid, False otherwise (with error messages printed)
|
||||
"""
|
||||
# Validate platform-specific dependencies first (exits if missing)
|
||||
validate_platform_dependencies()
|
||||
|
||||
valid = True
|
||||
|
||||
# Check for OAuth token (API keys are not supported)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Dependency Validator
|
||||
====================
|
||||
|
||||
Validates platform-specific dependencies are installed before running agents.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_platform_dependencies() -> None:
|
||||
"""
|
||||
Validate that platform-specific dependencies are installed.
|
||||
|
||||
Raises:
|
||||
SystemExit: If required platform-specific dependencies are missing,
|
||||
with helpful installation instructions.
|
||||
"""
|
||||
# Check Windows-specific dependencies
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
||||
try:
|
||||
import pywintypes # noqa: F401
|
||||
except ImportError:
|
||||
_exit_with_pywin32_error()
|
||||
|
||||
|
||||
def _exit_with_pywin32_error() -> None:
|
||||
"""Exit with helpful error message for missing pywin32."""
|
||||
# Use sys.prefix to detect the virtual environment path
|
||||
# This works for venv and poetry environments
|
||||
venv_activate = Path(sys.prefix) / "Scripts" / "activate"
|
||||
|
||||
sys.exit(
|
||||
"Error: Required Windows dependency 'pywin32' is not installed.\n"
|
||||
"\n"
|
||||
"Auto Claude requires pywin32 on Windows for LadybugDB/Graphiti memory integration.\n"
|
||||
"\n"
|
||||
"To fix this:\n"
|
||||
"1. Activate your virtual environment:\n"
|
||||
f" {venv_activate}\n"
|
||||
"\n"
|
||||
"2. Install pywin32:\n"
|
||||
" pip install pywin32>=306\n"
|
||||
"\n"
|
||||
" Or reinstall all dependencies:\n"
|
||||
" pip install -r requirements.txt\n"
|
||||
"\n"
|
||||
f"Current Python: {sys.executable}\n"
|
||||
)
|
||||
@@ -34,8 +34,25 @@ def _apply_ladybug_monkeypatch() -> bool:
|
||||
sys.modules["kuzu"] = real_ladybug
|
||||
logger.info("Applied LadybugDB monkeypatch (kuzu -> real_ladybug)")
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as e:
|
||||
logger.debug(f"LadybugDB import failed: {e}")
|
||||
# On Windows with Python 3.12+, provide more specific error details
|
||||
# (pywin32 is only required for Python 3.12+ per requirements.txt)
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
||||
# Check if it's the pywin32 error using both name attribute and string match
|
||||
# for robustness across Python versions
|
||||
is_pywin32_error = (
|
||||
(hasattr(e, "name") and e.name in ("pywintypes", "pywin32", "win32api"))
|
||||
or "pywintypes" in str(e)
|
||||
or "pywin32" in str(e)
|
||||
)
|
||||
if is_pywin32_error:
|
||||
logger.error(
|
||||
"LadybugDB requires pywin32 on Windows. "
|
||||
"Install with: pip install pywin32>=306"
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Windows-specific import issue: {e}")
|
||||
|
||||
# Fall back to native kuzu
|
||||
try:
|
||||
|
||||
@@ -10,6 +10,10 @@ tomli>=2.0.0; python_version < "3.11"
|
||||
real_ladybug>=0.13.0; python_version >= "3.12"
|
||||
graphiti-core>=0.5.0; python_version >= "3.12"
|
||||
|
||||
# Windows-specific dependency for LadybugDB/Graphiti
|
||||
# pywin32 provides Windows system bindings required by real_ladybug
|
||||
pywin32>=306; sys_platform == "win32" and python_version >= "3.12"
|
||||
|
||||
# Google AI (optional - for Gemini LLM and embeddings)
|
||||
google-generativeai>=0.8.0
|
||||
|
||||
|
||||
@@ -101,13 +101,11 @@ def mock_bot_detector(tmp_path):
|
||||
class TestPRReviewResult:
|
||||
"""Test PRReviewResult model."""
|
||||
|
||||
def test_save_and_load(self, temp_github_dir, sample_review_result):
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_and_load(self, temp_github_dir, sample_review_result):
|
||||
"""Test saving and loading review result."""
|
||||
# Save
|
||||
import asyncio
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
sample_review_result.save(temp_github_dir)
|
||||
)
|
||||
await sample_review_result.save(temp_github_dir)
|
||||
|
||||
# Verify file exists
|
||||
review_file = temp_github_dir / "pr" / f"review_{sample_review_result.pr_number}.json"
|
||||
@@ -337,14 +335,11 @@ class TestBotDetectionIntegration:
|
||||
class TestOrchestratorSkipLogic:
|
||||
"""Test orchestrator behavior when bot detection skips."""
|
||||
|
||||
def test_skip_returns_existing_review(self, temp_github_dir, sample_review_result):
|
||||
@pytest.mark.asyncio
|
||||
async def test_skip_returns_existing_review(self, temp_github_dir, sample_review_result):
|
||||
"""Test that skipping 'Already reviewed' returns existing review."""
|
||||
import asyncio
|
||||
|
||||
# Save existing review
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
sample_review_result.save(temp_github_dir)
|
||||
)
|
||||
await sample_review_result.save(temp_github_dir)
|
||||
|
||||
# Simulate the orchestrator logic for "Already reviewed" skip
|
||||
skip_reason = "Already reviewed commit abc123"
|
||||
@@ -466,19 +461,16 @@ class TestPostedFindingsTracking:
|
||||
assert sample_review_result.has_posted_findings is True
|
||||
assert len(sample_review_result.posted_finding_ids) == 1
|
||||
|
||||
def test_posted_findings_serialization(self, temp_github_dir, sample_review_result):
|
||||
@pytest.mark.asyncio
|
||||
async def test_posted_findings_serialization(self, temp_github_dir, sample_review_result):
|
||||
"""Test that posted findings are serialized correctly."""
|
||||
import asyncio
|
||||
|
||||
# Set posted findings
|
||||
sample_review_result.has_posted_findings = True
|
||||
sample_review_result.posted_finding_ids = ["finding-001"]
|
||||
sample_review_result.posted_at = "2025-01-01T10:00:00"
|
||||
|
||||
# Save
|
||||
asyncio.get_event_loop().run_until_complete(
|
||||
sample_review_result.save(temp_github_dir)
|
||||
)
|
||||
await sample_review_result.save(temp_github_dir)
|
||||
|
||||
# Load and verify
|
||||
loaded = PRReviewResult.load(temp_github_dir, sample_review_result.pr_number)
|
||||
|
||||
Reference in New Issue
Block a user