1
+ mod finder;
1
2
mod templatetags;
2
3
3
4
pub use templatetags:: TemplateTags ;
@@ -6,7 +7,6 @@ use pyo3::prelude::*;
6
7
use std:: env;
7
8
use std:: fmt;
8
9
use std:: path:: { Path , PathBuf } ;
9
- use which:: which;
10
10
11
11
#[ derive( Debug ) ]
12
12
pub struct DjangoProject {
@@ -126,7 +126,6 @@ impl PythonEnvironment {
126
126
#[ cfg( windows) ]
127
127
let python_path = prefix. join ( "Scripts" ) . join ( "python.exe" ) ;
128
128
129
- // Check if the *prefix* and the *binary* exist.
130
129
if !prefix. is_dir ( ) || !python_path. exists ( ) {
131
130
return None ;
132
131
}
@@ -137,10 +136,9 @@ impl PythonEnvironment {
137
136
let bin_dir = prefix. join ( "Scripts" ) ;
138
137
139
138
let mut sys_path = Vec :: new ( ) ;
140
- sys_path. push ( bin_dir) ; // Add bin/ or Scripts/
139
+ sys_path. push ( bin_dir) ;
141
140
142
141
if let Some ( site_packages) = Self :: find_site_packages ( prefix) {
143
- // Check existence inside the if let, as find_site_packages might return a path that doesn't exist
144
142
if site_packages. is_dir ( ) {
145
143
sys_path. push ( site_packages) ;
146
144
}
@@ -167,12 +165,10 @@ impl PythonEnvironment {
167
165
}
168
166
169
167
fn from_system_python ( ) -> Option < Self > {
170
- let python_path = match which ( "python" ) {
168
+ let python_path = match finder :: find ( "python" ) {
171
169
Ok ( p) => p,
172
170
Err ( _) => return None ,
173
171
} ;
174
- // which() might return a path inside a bin/Scripts dir, or directly the executable
175
- // We need the prefix, which is usually two levels up from the executable in standard layouts
176
172
let bin_dir = python_path. parent ( ) ?;
177
173
let prefix = bin_dir. parent ( ) ?;
178
174
@@ -232,11 +228,13 @@ mod tests {
232
228
use super :: * ;
233
229
use std:: fs;
234
230
#[ cfg( unix) ]
235
- use std:: os:: unix:: fs:: PermissionsExt ; // Needed for setting execute permission
231
+ use std:: os:: unix:: fs:: PermissionsExt ;
236
232
use tempfile:: tempdir;
237
233
238
234
mod env_discovery {
235
+ use super :: finder:: mock:: { self as exec_mock, MockGuard } ;
239
236
use super :: * ;
237
+ use which:: Error as WhichError ;
240
238
241
239
fn create_mock_venv ( dir : & Path , version : Option < & str > ) -> PathBuf {
242
240
let prefix = dir. to_path_buf ( ) ;
@@ -245,7 +243,7 @@ mod tests {
245
243
{
246
244
let bin_dir = prefix. join ( "bin" ) ;
247
245
fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
248
- fs:: write ( bin_dir. join ( "python" ) , "" ) . unwrap ( ) ; // Create dummy executable
246
+ fs:: write ( bin_dir. join ( "python" ) , "" ) . unwrap ( ) ;
249
247
let lib_dir = prefix. join ( "lib" ) ;
250
248
fs:: create_dir_all ( & lib_dir) . unwrap ( ) ;
251
249
let py_version_dir = lib_dir. join ( version. unwrap_or ( "python3.9" ) ) ;
@@ -256,7 +254,7 @@ mod tests {
256
254
{
257
255
let bin_dir = prefix. join ( "Scripts" ) ;
258
256
fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
259
- fs:: write ( bin_dir. join ( "python.exe" ) , "" ) . unwrap ( ) ; // Create dummy executable
257
+ fs:: write ( bin_dir. join ( "python.exe" ) , "" ) . unwrap ( ) ;
260
258
let lib_dir = prefix. join ( "Lib" ) ;
261
259
fs:: create_dir_all ( & lib_dir) . unwrap ( ) ;
262
260
fs:: create_dir_all ( lib_dir. join ( "site-packages" ) ) . unwrap ( ) ;
@@ -291,29 +289,6 @@ mod tests {
291
289
}
292
290
}
293
291
294
- // Guard struct to temporarily modify the PATH environment variable
295
- struct PathGuard {
296
- original_path : Option < String > ,
297
- }
298
-
299
- impl PathGuard {
300
- fn set ( new_path_val : & str ) -> Self {
301
- let original_path = env:: var ( "PATH" ) . ok ( ) ;
302
- env:: set_var ( "PATH" , new_path_val) ;
303
- Self { original_path }
304
- }
305
- }
306
-
307
- impl Drop for PathGuard {
308
- fn drop ( & mut self ) {
309
- // Restore original PATH, or remove if it wasn't set initially
310
- match self . original_path . as_deref ( ) {
311
- Some ( val) => env:: set_var ( "PATH" , val) ,
312
- None => env:: remove_var ( "PATH" ) ,
313
- }
314
- }
315
- }
316
-
317
292
#[ test]
318
293
fn test_explicit_venv_path_found ( ) {
319
294
let project_dir = tempdir ( ) . unwrap ( ) ;
@@ -442,17 +417,15 @@ mod tests {
442
417
}
443
418
444
419
#[ test]
445
- // #[ignore = "Relies on system python being available and having standard layout"] // No longer ignored!
446
420
fn test_system_python_fallback ( ) {
447
- let project_dir = tempdir ( ) . unwrap ( ) ; // Dummy project dir, not used for discovery here.
421
+ let project_dir = tempdir ( ) . unwrap ( ) ;
448
422
449
- // Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
423
+ // Set VIRTUAL_ENV to invalid to ensure it's ignored.
450
424
let invalid_virtual_env_path =
451
425
project_dir. path ( ) . join ( "non_existent_venv_sys_fallback" ) ;
452
426
let _guard =
453
427
VirtualEnvGuard :: set ( "VIRTUAL_ENV" , invalid_virtual_env_path. to_str ( ) . unwrap ( ) ) ;
454
428
455
- // --- Set up mock system python ---
456
429
let mock_sys_python_dir = tempdir ( ) . unwrap ( ) ;
457
430
let mock_sys_python_prefix = mock_sys_python_dir. path ( ) ;
458
431
@@ -472,29 +445,23 @@ mod tests {
472
445
let bin_dir = mock_sys_python_prefix. join ( bin_subdir) ;
473
446
fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
474
447
let python_path = bin_dir. join ( python_exe) ;
475
- fs:: write ( & python_path, "" ) . unwrap ( ) ; // Create dummy executable
448
+ fs:: write ( & python_path, "" ) . unwrap ( ) ;
476
449
477
450
// Ensure the dummy executable has execute permissions (required by `which` on Unix)
478
451
#[ cfg( unix) ]
479
452
{
480
453
let mut perms = fs:: metadata ( & python_path) . unwrap ( ) . permissions ( ) ;
481
- perms. set_mode ( 0o755 ) ; // rwxr-xr-x
454
+ perms. set_mode ( 0o755 ) ;
482
455
fs:: set_permissions ( & python_path, perms) . unwrap ( ) ;
483
456
}
484
457
485
458
let site_packages_path = mock_sys_python_prefix. join ( site_packages_rel_path) ;
486
459
fs:: create_dir_all ( & site_packages_path) . unwrap ( ) ;
487
460
488
- // --- Manipulate PATH ---
489
- // Completely overwrite PATH to only include the mock bin directory
490
- let canonical_bin_dir =
491
- bin_dir. canonicalize ( ) . expect ( "Failed to canonicalize mock bin dir" ) ;
492
- let new_path = canonical_bin_dir. to_str ( ) . unwrap ( ) . to_string ( ) ;
493
- let _path_guard = PathGuard :: set ( & new_path) ;
461
+ let _guard = MockGuard ;
462
+ exec_mock:: set_mock_path ( "python" , python_path. clone ( ) ) ;
494
463
495
464
// We don't create any venvs in project_dir
496
-
497
- // This test assumes `which python` works and points to a standard layout
498
465
let system_env = PythonEnvironment :: new ( project_dir. path ( ) , None ) ;
499
466
500
467
assert ! (
@@ -503,10 +470,12 @@ mod tests {
503
470
) ;
504
471
505
472
if let Some ( env) = system_env {
506
- assert_eq ! ( env. python_path, python_path, "Python path should match mock" ) ;
507
473
assert_eq ! (
508
- env. sys_prefix,
509
- mock_sys_python_prefix,
474
+ env. python_path, python_path,
475
+ "Python path should match mock"
476
+ ) ;
477
+ assert_eq ! (
478
+ env. sys_prefix, mock_sys_python_prefix,
510
479
"Sys prefix should match mock prefix"
511
480
) ;
512
481
assert ! (
@@ -517,8 +486,7 @@ mod tests {
517
486
env. sys_path. contains( & site_packages_path) ,
518
487
"Sys path should contain mock site-packages"
519
488
) ;
520
- } else {
521
- panic ! ( "Expected to find environment, but got None" ) ; // Should not happen if assert above passed
489
+ panic ! ( "Expected to find environment, but got None" ) ;
522
490
}
523
491
}
524
492
@@ -528,17 +496,18 @@ mod tests {
528
496
529
497
// Ensure no explicit path, no project venvs, and set VIRTUAL_ENV to invalid.
530
498
let invalid_virtual_env_path = project_dir. path ( ) . join ( "non_existent_venv_no_python" ) ;
531
- let _guard =
499
+ let _guard_venv =
532
500
VirtualEnvGuard :: set ( "VIRTUAL_ENV" , invalid_virtual_env_path. to_str ( ) . unwrap ( ) ) ;
533
501
534
- // Overwrite PATH with an empty value to ensure `which("python")` fails.
535
- let _path_guard = PathGuard :: set ( "" ) ;
502
+ let _guard_mock = MockGuard ;
503
+ exec_mock :: set_mock_error ( "python" , WhichError :: CannotFindBinaryPath ) ;
536
504
537
- // Call the function under test
538
505
let env = PythonEnvironment :: new ( project_dir. path ( ) , None ) ;
539
506
540
- // Assert that no environment was found
541
- assert ! ( env. is_none( ) , "Expected no environment to be found when all discovery methods fail" ) ;
507
+ assert ! (
508
+ env. is_none( ) ,
509
+ "Expected no environment to be found when all discovery methods fail"
510
+ ) ;
542
511
}
543
512
544
513
#[ test]
0 commit comments