pdd-cli 0.0.43__py3-none-any.whl → 0.0.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pdd/pytest_output.py CHANGED
@@ -3,9 +3,11 @@ import json
3
3
  import io
4
4
  import sys
5
5
  import pytest
6
+ import subprocess
6
7
  from rich.console import Console
7
8
  from rich.pretty import pprint
8
9
  import os
10
+ from .python_env_detector import detect_host_python_executable
9
11
 
10
12
  console = Console()
11
13
 
@@ -80,27 +82,77 @@ def run_pytest_and_capture_output(test_file: str) -> dict:
80
82
  )
81
83
  return {}
82
84
 
83
- collector = TestResultCollector()
85
+ # Use environment-aware Python executable for pytest execution
86
+ python_executable = detect_host_python_executable()
87
+
84
88
  try:
85
- collector.capture_logs()
86
- result = pytest.main([test_file], plugins=[collector])
87
- finally:
88
- stdout, stderr = collector.get_logs()
89
-
90
- return {
91
- "test_file": test_file,
92
- "test_results": [
93
- {
94
- "standard_output": stdout,
95
- "standard_error": stderr,
96
- "return_code": int(result),
97
- "warnings": collector.warnings,
98
- "errors": collector.errors,
99
- "failures": collector.failures,
100
- "passed": collector.passed,
101
- }
102
- ],
103
- }
89
+ # Run pytest using subprocess with the detected Python executable
90
+ result = subprocess.run(
91
+ [python_executable, "-m", "pytest", test_file, "-v"],
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=300
95
+ )
96
+
97
+ stdout = result.stdout
98
+ stderr = result.stderr
99
+ return_code = result.returncode
100
+
101
+ # Parse the output to extract test results
102
+ # Count passed, failed, and skipped tests from the output
103
+ passed = stdout.count(" PASSED")
104
+ failures = stdout.count(" FAILED") + stdout.count(" ERROR")
105
+ errors = 0 # Will be included in failures for subprocess execution
106
+ warnings = stdout.count("warning")
107
+
108
+ # If return code is 2, it indicates a pytest error
109
+ if return_code == 2:
110
+ errors = 1
111
+
112
+ return {
113
+ "test_file": test_file,
114
+ "test_results": [
115
+ {
116
+ "standard_output": stdout,
117
+ "standard_error": stderr,
118
+ "return_code": return_code,
119
+ "warnings": warnings,
120
+ "errors": errors,
121
+ "failures": failures,
122
+ "passed": passed,
123
+ }
124
+ ],
125
+ }
126
+ except subprocess.TimeoutExpired:
127
+ return {
128
+ "test_file": test_file,
129
+ "test_results": [
130
+ {
131
+ "standard_output": "",
132
+ "standard_error": "Test execution timed out",
133
+ "return_code": -1,
134
+ "warnings": 0,
135
+ "errors": 1,
136
+ "failures": 0,
137
+ "passed": 0,
138
+ }
139
+ ],
140
+ }
141
+ except Exception as e:
142
+ return {
143
+ "test_file": test_file,
144
+ "test_results": [
145
+ {
146
+ "standard_output": "",
147
+ "standard_error": f"Error running pytest: {str(e)}",
148
+ "return_code": -1,
149
+ "warnings": 0,
150
+ "errors": 1,
151
+ "failures": 0,
152
+ "passed": 0,
153
+ }
154
+ ],
155
+ }
104
156
 
105
157
  def save_output_to_json(output: dict, output_file: str = "pytest.json"):
106
158
  """
@@ -0,0 +1,151 @@
1
+ """
2
+ Python Environment Detector
3
+
4
+ Detects the host shell's Python environment (conda, venv, poetry, pipenv, etc.)
5
+ and returns the appropriate Python executable for subprocess calls.
6
+
7
+ This ensures that PDD operations use the same Python environment as the shell
8
+ that launched PDD, rather than the uv tools environment where PDD is installed.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import shutil
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+
18
+ def detect_host_python_executable() -> str:
19
+ """
20
+ Detect the host shell's Python executable.
21
+
22
+ This function checks for various virtual environment indicators
23
+ and returns the appropriate Python executable path.
24
+
25
+ Returns:
26
+ str: Path to the Python executable that should be used for subprocess calls.
27
+ Falls back to sys.executable if no host environment is detected.
28
+
29
+ Detection order:
30
+ 1. VIRTUAL_ENV (works for venv, virtualenv, poetry, pipenv)
31
+ 2. CONDA_PREFIX (conda-specific)
32
+ 3. PATH resolution with shutil.which('python')
33
+ 4. sys.executable (fallback)
34
+ """
35
+
36
+ # Check for virtual environment (venv, virtualenv, poetry, pipenv)
37
+ virtual_env = os.environ.get('VIRTUAL_ENV')
38
+ if virtual_env:
39
+ # Try common Python executable locations within the virtual environment
40
+ for python_name in ['python', 'python3']:
41
+ # Unix-like systems
42
+ venv_python = Path(virtual_env) / 'bin' / python_name
43
+ if venv_python.is_file():
44
+ return str(venv_python)
45
+
46
+ # Windows
47
+ venv_python = Path(virtual_env) / 'Scripts' / f'{python_name}.exe'
48
+ if venv_python.is_file():
49
+ return str(venv_python)
50
+
51
+ # Check for conda environment
52
+ conda_prefix = os.environ.get('CONDA_PREFIX')
53
+ if conda_prefix:
54
+ # Try common Python executable locations within conda environment
55
+ for python_name in ['python', 'python3']:
56
+ # Unix-like systems
57
+ conda_python = Path(conda_prefix) / 'bin' / python_name
58
+ if conda_python.is_file():
59
+ return str(conda_python)
60
+
61
+ # Windows
62
+ conda_python = Path(conda_prefix) / f'{python_name}.exe'
63
+ if conda_python.is_file():
64
+ return str(conda_python)
65
+
66
+ # Use PATH resolution as fallback (respects shell's PATH modifications)
67
+ which_python = shutil.which('python')
68
+ if which_python and Path(which_python).resolve() != Path(sys.executable).resolve():
69
+ # Only use if it's different from the current sys.executable
70
+ # This helps detect when we're in a different environment
71
+ return which_python
72
+
73
+ # Try python3 as well
74
+ which_python3 = shutil.which('python3')
75
+ if which_python3 and Path(which_python3).resolve() != Path(sys.executable).resolve():
76
+ return which_python3
77
+
78
+ # Final fallback to current executable
79
+ return sys.executable
80
+
81
+
82
+ def get_environment_info() -> dict:
83
+ """
84
+ Get detailed information about the current Python environment.
85
+
86
+ Returns:
87
+ dict: Dictionary containing environment information for debugging
88
+ """
89
+ return {
90
+ 'sys_executable': sys.executable,
91
+ 'detected_executable': detect_host_python_executable(),
92
+ 'virtual_env': os.environ.get('VIRTUAL_ENV'),
93
+ 'conda_prefix': os.environ.get('CONDA_PREFIX'),
94
+ 'conda_default_env': os.environ.get('CONDA_DEFAULT_ENV'),
95
+ 'poetry_active': os.environ.get('POETRY_ACTIVE'),
96
+ 'pipenv_active': os.environ.get('PIPENV_ACTIVE'),
97
+ 'which_python': shutil.which('python'),
98
+ 'which_python3': shutil.which('python3'),
99
+ 'path': os.environ.get('PATH', '').split(os.pathsep)[:3], # First 3 PATH entries
100
+ }
101
+
102
+
103
+ def is_in_virtual_environment() -> bool:
104
+ """
105
+ Check if we're currently running in any kind of virtual environment.
106
+
107
+ Returns:
108
+ bool: True if in a virtual environment, False otherwise
109
+ """
110
+ return bool(
111
+ os.environ.get('VIRTUAL_ENV') or
112
+ os.environ.get('CONDA_PREFIX') or
113
+ os.environ.get('POETRY_ACTIVE') or
114
+ os.environ.get('PIPENV_ACTIVE')
115
+ )
116
+
117
+
118
+ def get_environment_type() -> str:
119
+ """
120
+ Determine the type of virtual environment we're in.
121
+
122
+ Returns:
123
+ str: Type of environment ('conda', 'venv', 'poetry', 'pipenv', 'system', 'unknown')
124
+ """
125
+ if os.environ.get('CONDA_PREFIX'):
126
+ return 'conda'
127
+ elif os.environ.get('POETRY_ACTIVE'):
128
+ return 'poetry'
129
+ elif os.environ.get('PIPENV_ACTIVE'):
130
+ return 'pipenv'
131
+ elif os.environ.get('VIRTUAL_ENV'):
132
+ return 'venv'
133
+ elif is_in_virtual_environment():
134
+ return 'unknown'
135
+ else:
136
+ return 'system'
137
+
138
+
139
+ if __name__ == '__main__':
140
+ # Demo/test functionality
141
+ print("Python Environment Detection")
142
+ print("=" * 40)
143
+
144
+ env_info = get_environment_info()
145
+ for key, value in env_info.items():
146
+ print(f"{key}: {value}")
147
+
148
+ print()
149
+ print(f"Environment type: {get_environment_type()}")
150
+ print(f"In virtual environment: {is_in_virtual_environment()}")
151
+ print(f"Detected Python executable: {detect_host_python_executable()}")
@@ -1,5 +1,11 @@
1
1
  from typing import Optional, Tuple
2
- from datetime import datetime, UTC
2
+ from datetime import datetime
3
+ try:
4
+ from datetime import UTC
5
+ except ImportError:
6
+ # Python < 3.11 compatibility
7
+ from datetime import timezone
8
+ UTC = timezone.utc
3
9
  from io import StringIO
4
10
  import os
5
11
  import glob