5
5
import os
6
6
import plistlib
7
7
import re
8
- import shutil
9
8
import subprocess
10
9
import sys
11
10
18
17
import virtualenv
19
18
20
19
from cleo .io .null_io import NullIO
21
- from cleo .io .outputs .output import Verbosity
22
20
from poetry .core .constraints .version import Version
23
- from poetry .core .constraints .version import parse_constraint
24
21
25
22
from poetry .toml .file import TOMLFile
26
23
from poetry .utils ._compat import WINDOWS
31
28
from poetry .utils .env .exceptions import NoCompatiblePythonVersionFound
32
29
from poetry .utils .env .exceptions import PythonVersionNotFound
33
30
from poetry .utils .env .generic_env import GenericEnv
31
+ from poetry .utils .env .python_manager import Python
34
32
from poetry .utils .env .script_strings import GET_ENV_PATH_ONELINER
35
33
from poetry .utils .env .script_strings import GET_PYTHON_VERSION_ONELINER
36
34
from poetry .utils .env .system_env import SystemEnv
@@ -97,70 +95,6 @@ def __init__(self, poetry: Poetry, io: None | IO = None) -> None:
97
95
self ._poetry = poetry
98
96
self ._io = io or NullIO ()
99
97
100
- @staticmethod
101
- def _full_python_path (python : str ) -> Path | None :
102
- # eg first find pythonXY.bat on windows.
103
- path_python = shutil .which (python )
104
- if path_python is None :
105
- return None
106
-
107
- try :
108
- encoding = "locale" if sys .version_info >= (3 , 10 ) else None
109
- executable = subprocess .check_output (
110
- [path_python , "-c" , "import sys; print(sys.executable)" ],
111
- text = True ,
112
- encoding = encoding ,
113
- ).strip ()
114
- return Path (executable )
115
-
116
- except CalledProcessError :
117
- return None
118
-
119
- @staticmethod
120
- def _detect_active_python (io : None | IO = None ) -> Path | None :
121
- io = io or NullIO ()
122
- io .write_error_line (
123
- "Trying to detect current active python executable as specified in"
124
- " the config." ,
125
- verbosity = Verbosity .VERBOSE ,
126
- )
127
-
128
- executable = EnvManager ._full_python_path ("python" )
129
-
130
- if executable is not None :
131
- io .write_error_line (f"Found: { executable } " , verbosity = Verbosity .VERBOSE )
132
- else :
133
- io .write_error_line (
134
- "Unable to detect the current active python executable. Falling"
135
- " back to default." ,
136
- verbosity = Verbosity .VERBOSE ,
137
- )
138
-
139
- return executable
140
-
141
- @staticmethod
142
- def get_python_version (
143
- precision : int = 3 ,
144
- prefer_active_python : bool = False ,
145
- io : None | IO = None ,
146
- ) -> Version :
147
- version = "." .join (str (v ) for v in sys .version_info [:precision ])
148
-
149
- if prefer_active_python :
150
- executable = EnvManager ._detect_active_python (io )
151
-
152
- if executable :
153
- encoding = "locale" if sys .version_info >= (3 , 10 ) else None
154
- python_patch = subprocess .check_output (
155
- [executable , "-c" , GET_PYTHON_VERSION_ONELINER ],
156
- text = True ,
157
- encoding = encoding ,
158
- ).strip ()
159
-
160
- version = "." .join (str (v ) for v in python_patch .split ("." )[:precision ])
161
-
162
- return Version .parse (version )
163
-
164
98
@property
165
99
def in_project_venv (self ) -> Path :
166
100
venv : Path = self ._poetry .file .path .parent / ".venv"
@@ -189,24 +123,10 @@ def activate(self, python: str) -> Env:
189
123
# Executable in PATH or full executable path
190
124
pass
191
125
192
- python_path = self . _full_python_path (python )
193
- if python_path is None :
126
+ python_ = Python . get_by_name (python )
127
+ if python_ is None :
194
128
raise PythonVersionNotFound (python )
195
129
196
- try :
197
- encoding = "locale" if sys .version_info >= (3 , 10 ) else None
198
- python_version_string = subprocess .check_output (
199
- [python_path , "-c" , GET_PYTHON_VERSION_ONELINER ],
200
- text = True ,
201
- encoding = encoding ,
202
- )
203
- except CalledProcessError as e :
204
- raise EnvCommandError (e )
205
-
206
- python_version = Version .parse (python_version_string .strip ())
207
- minor = f"{ python_version .major } .{ python_version .minor } "
208
- patch = python_version .text
209
-
210
130
create = False
211
131
# If we are required to create the virtual environment in the project directory,
212
132
# create or recreate it if needed
@@ -218,10 +138,10 @@ def activate(self, python: str) -> Env:
218
138
_venv = VirtualEnv (venv )
219
139
current_patch = "." .join (str (v ) for v in _venv .version_info [:3 ])
220
140
221
- if patch != current_patch :
141
+ if python_ . patch_version . to_string () != current_patch :
222
142
create = True
223
143
224
- self .create_venv (executable = python_path , force = create )
144
+ self .create_venv (executable = python_ . executable , force = create )
225
145
226
146
return self .get (reload = True )
227
147
@@ -233,11 +153,14 @@ def activate(self, python: str) -> Env:
233
153
current_minor = current_env ["minor" ]
234
154
current_patch = current_env ["patch" ]
235
155
236
- if current_minor == minor and current_patch != patch :
156
+ if (
157
+ current_minor == python_ .minor_version .to_string ()
158
+ and current_patch != python_ .patch_version .to_string ()
159
+ ):
237
160
# We need to recreate
238
161
create = True
239
162
240
- name = f"{ self .base_env_name } -py{ minor } "
163
+ name = f"{ self .base_env_name } -py{ python_ . minor_version . to_string () } "
241
164
venv = venv_path / name
242
165
243
166
# Create if needed
@@ -251,13 +174,16 @@ def activate(self, python: str) -> Env:
251
174
_venv = VirtualEnv (venv )
252
175
current_patch = "." .join (str (v ) for v in _venv .version_info [:3 ])
253
176
254
- if patch != current_patch :
177
+ if python_ . patch_version . to_string () != current_patch :
255
178
create = True
256
179
257
- self .create_venv (executable = python_path , force = create )
180
+ self .create_venv (executable = python_ . executable , force = create )
258
181
259
182
# Activate
260
- envs [self .base_env_name ] = {"minor" : minor , "patch" : patch }
183
+ envs [self .base_env_name ] = {
184
+ "minor" : python_ .minor_version .to_string (),
185
+ "patch" : python_ .patch_version .to_string (),
186
+ }
261
187
self .envs_file .write (envs )
262
188
263
189
return self .get (reload = True )
@@ -277,12 +203,8 @@ def get(self, reload: bool = False) -> Env:
277
203
if self ._env is not None and not reload :
278
204
return self ._env
279
205
280
- prefer_active_python = self ._poetry .config .get (
281
- "virtualenvs.prefer-active-python"
282
- )
283
- python_minor = self .get_python_version (
284
- precision = 2 , prefer_active_python = prefer_active_python , io = self ._io
285
- ).to_string ()
206
+ python = Python .get_preferred_python (config = self ._poetry .config , io = self ._io )
207
+ python_minor = python .minor_version .to_string ()
286
208
287
209
env = None
288
210
envs = None
@@ -480,8 +402,11 @@ def create_venv(
480
402
)
481
403
venv_prompt = self ._poetry .config .get ("virtualenvs.prompt" )
482
404
483
- if not executable and prefer_active_python :
484
- executable = self ._detect_active_python ()
405
+ python = (
406
+ Python (executable )
407
+ if executable
408
+ else Python .get_preferred_python (config = self ._poetry .config , io = self ._io )
409
+ )
485
410
486
411
venv_path = (
487
412
self .in_project_venv
@@ -491,19 +416,8 @@ def create_venv(
491
416
if not name :
492
417
name = self ._poetry .package .name
493
418
494
- python_patch = "." .join ([str (v ) for v in sys .version_info [:3 ]])
495
- python_minor = "." .join ([str (v ) for v in sys .version_info [:2 ]])
496
- if executable :
497
- encoding = "locale" if sys .version_info >= (3 , 10 ) else None
498
- python_patch = subprocess .check_output (
499
- [executable , "-c" , GET_PYTHON_VERSION_ONELINER ],
500
- text = True ,
501
- encoding = encoding ,
502
- ).strip ()
503
- python_minor = "." .join (python_patch .split ("." )[:2 ])
504
-
505
419
supported_python = self ._poetry .package .python_constraint
506
- if not supported_python .allows (Version . parse ( python_patch ) ):
420
+ if not supported_python .allows (python . patch_version ):
507
421
# The currently activated or chosen Python version
508
422
# is not compatible with the Python constraint specified
509
423
# for the project.
@@ -512,71 +426,29 @@ def create_venv(
512
426
# Otherwise, we try to find a compatible Python version.
513
427
if executable and not prefer_active_python :
514
428
raise NoCompatiblePythonVersionFound (
515
- self ._poetry .package .python_versions , python_patch
429
+ self ._poetry .package .python_versions ,
430
+ python .patch_version .to_string (),
516
431
)
517
432
518
433
self ._io .write_error_line (
519
- f"<warning>The currently activated Python version { python_patch } is not"
434
+ f"<warning>The currently activated Python version { python . patch_version . to_string () } is not"
520
435
f" supported by the project ({ self ._poetry .package .python_versions } ).\n "
521
436
"Trying to find and use a compatible version.</warning> "
522
437
)
523
438
524
- for suffix in sorted (
525
- self ._poetry .package .AVAILABLE_PYTHONS ,
526
- key = lambda v : (v .startswith ("3" ), - len (v ), v ),
527
- reverse = True ,
528
- ):
529
- if len (suffix ) == 1 :
530
- if not parse_constraint (f"^{ suffix } .0" ).allows_any (
531
- supported_python
532
- ):
533
- continue
534
- elif not supported_python .allows_any (parse_constraint (suffix + ".*" )):
535
- continue
536
-
537
- python_name = f"python{ suffix } "
538
- if self ._io .is_debug ():
539
- self ._io .write_error_line (f"<debug>Trying { python_name } </debug>" )
540
-
541
- python = self ._full_python_path (python_name )
542
- if python is None :
543
- continue
544
-
545
- try :
546
- encoding = "locale" if sys .version_info >= (3 , 10 ) else None
547
- python_patch = subprocess .check_output (
548
- [python , "-c" , GET_PYTHON_VERSION_ONELINER ],
549
- stderr = subprocess .STDOUT ,
550
- text = True ,
551
- encoding = encoding ,
552
- ).strip ()
553
- except CalledProcessError :
554
- continue
555
-
556
- if supported_python .allows (Version .parse (python_patch )):
557
- self ._io .write_error_line (
558
- f"Using <c1>{ python_name } </c1> ({ python_patch } )"
559
- )
560
- executable = python
561
- python_minor = "." .join (python_patch .split ("." )[:2 ])
562
- break
563
-
564
- if not executable :
565
- raise NoCompatiblePythonVersionFound (
566
- self ._poetry .package .python_versions
567
- )
439
+ python = Python .get_compatible_python (poetry = self ._poetry , io = self ._io )
568
440
569
441
if in_project_venv :
570
442
venv = venv_path
571
443
else :
572
444
name = self .generate_env_name (name , str (cwd ))
573
- name = f"{ name } -py{ python_minor . strip ()} "
445
+ name = f"{ name } -py{ python . minor_version . to_string ()} "
574
446
venv = venv_path / name
575
447
576
448
if venv_prompt is not None :
577
449
venv_prompt = venv_prompt .format (
578
450
project_name = self ._poetry .package .name or "virtualenv" ,
579
- python_version = python_minor ,
451
+ python_version = python . minor_version . to_string () ,
580
452
)
581
453
582
454
if not venv .exists ():
@@ -613,7 +485,7 @@ def create_venv(
613
485
if create_venv :
614
486
self .build_venv (
615
487
venv ,
616
- executable = executable ,
488
+ executable = python . executable ,
617
489
flags = self ._poetry .config .get ("virtualenvs.options" ),
618
490
prompt = venv_prompt ,
619
491
)
0 commit comments