Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork931
Expand file tree
/
Copy pathpython.py
More file actions
228 lines (182 loc) · 7.15 KB
/
python.py
File metadata and controls
228 lines (182 loc) · 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
from __future__importannotations
importcontextlib
importfunctools
importos
importsys
fromcollections.abcimportGenerator
fromcollections.abcimportSequence
importpre_commit.constantsasC
frompre_commitimportlang_base
frompre_commit.envcontextimportenvcontext
frompre_commit.envcontextimportPatchesT
frompre_commit.envcontextimportUNSET
frompre_commit.envcontextimportVar
frompre_commit.parse_shebangimportfind_executable
frompre_commit.prefiximportPrefix
frompre_commit.utilimportCalledProcessError
frompre_commit.utilimportcmd_output
frompre_commit.utilimportcmd_output_b
frompre_commit.utilimportwin_exe
ENVIRONMENT_DIR='py_env'
run_hook=lang_base.basic_run_hook
@functools.cache
def_version_info(exe:str)->str:
prog='import sys;print(".".join(str(p) for p in sys.version_info))'
try:
returncmd_output(exe,'-S','-c',prog)[1].strip()
exceptCalledProcessError:
returnf'<<error retrieving version from{exe}>>'
def_read_pyvenv_cfg(filename:str)->dict[str,str]:
ret= {}
withopen(filename,encoding='UTF-8')asf:
forlineinf:
try:
k,v=line.split('=')
exceptValueError:# blank line / comment / etc.
continue
else:
ret[k.strip()]=v.strip()
returnret
defbin_dir(venv:str)->str:
"""On windows there's a different directory for the virtualenv"""
bin_part='Scripts'ifsys.platform=='win32'else'bin'
returnos.path.join(venv,bin_part)
defget_env_patch(venv:str)->PatchesT:
return (
('PIP_DISABLE_PIP_VERSION_CHECK','1'),
('PYTHONHOME',UNSET),
('VIRTUAL_ENV',venv),
('PATH', (bin_dir(venv),os.pathsep,Var('PATH'))),
)
def_find_by_py_launcher(
version:str,
)->str|None:# pragma: no cover (windows only)
ifversion.startswith('python'):
num=version.removeprefix('python')
cmd= ('py',f'-{num}','-c','import sys; print(sys.executable)')
env=dict(os.environ,PYTHONIOENCODING='UTF-8')
try:
returncmd_output(*cmd,env=env)[1].strip()
exceptCalledProcessError:
pass
returnNone
def_impl_exe_name()->str:
ifsys.implementation.name=='cpython':# pragma: cpython cover
return'python'
else:# pragma: cpython no cover
returnsys.implementation.name# pypy mostly
def_find_by_sys_executable()->str|None:
def_norm(path:str)->str|None:
_,exe=os.path.split(path.lower())
exe,_,_=exe.partition('.exe')
ifexenotin {'python','pythonw'}andfind_executable(exe):
returnexe
returnNone
# On linux, I see these common sys.executables:
#
# system `python`: /usr/bin/python -> python2.7
# system `python2`: /usr/bin/python2 -> python2.7
# virtualenv v: v/bin/python (will not return from this loop)
# virtualenv v -ppython2: v/bin/python -> python2
# virtualenv v -ppython2.7: v/bin/python -> python2.7
# virtualenv v -ppypy: v/bin/python -> v/bin/pypy
forpathin (sys.executable,os.path.realpath(sys.executable)):
exe=_norm(path)
ifexe:
returnexe
returnNone
@functools.lru_cache(maxsize=1)
defget_default_version()->str:# pragma: no cover (platform dependent)
v_major=f'{sys.version_info[0]}'
v_minor=f'{sys.version_info[0]}.{sys.version_info[1]}'
# attempt the likely implementation exe
forpotentialin (v_minor,v_major):
exe=f'{_impl_exe_name()}{potential}'
iffind_executable(exe):
returnexe
# next try `sys.executable` (or the realpath)
maybe_exe=_find_by_sys_executable()
ifmaybe_exe:
returnmaybe_exe
# maybe on windows we can find it via py launcher?
ifsys.platform=='win32':# pragma: win32 cover
exe=f'python{v_minor}'
if_find_by_py_launcher(exe):
returnexe
# We tried!
returnC.DEFAULT
def_sys_executable_matches(version:str)->bool:
ifversion=='python':
returnTrue
elifnotversion.startswith('python'):
returnFalse
try:
info=tuple(int(p)forpinversion.removeprefix('python').split('.'))
exceptValueError:
returnFalse
returnsys.version_info[:len(info)]==info
defnorm_version(version:str)->str|None:
ifversion==C.DEFAULT:# use virtualenv's default
returnNone
elif_sys_executable_matches(version):# virtualenv defaults to our exe
returnNone
ifsys.platform=='win32':# pragma: no cover (windows)
version_exec=_find_by_py_launcher(version)
ifversion_exec:
returnversion_exec
# Try looking up by name
version_exec=find_executable(version)
ifversion_execandversion_exec!=version:
returnversion_exec
# Otherwise assume it is a path
returnos.path.expanduser(version)
@contextlib.contextmanager
defin_env(prefix:Prefix,version:str)->Generator[None]:
envdir=lang_base.environment_dir(prefix,ENVIRONMENT_DIR,version)
withenvcontext(get_env_patch(envdir)):
yield
defhealth_check(prefix:Prefix,version:str)->str|None:
envdir=lang_base.environment_dir(prefix,ENVIRONMENT_DIR,version)
pyvenv_cfg=os.path.join(envdir,'pyvenv.cfg')
# created with "old" virtualenv
ifnotos.path.exists(pyvenv_cfg):
return'pyvenv.cfg does not exist (old virtualenv?)'
exe_name=win_exe('python')
py_exe=prefix.path(bin_dir(envdir),exe_name)
cfg=_read_pyvenv_cfg(pyvenv_cfg)
if'version_info'notincfg:
return"created virtualenv's pyvenv.cfg is missing `version_info`"
# always use uncached lookup here in case we replaced an unhealthy env
virtualenv_version=_version_info.__wrapped__(py_exe)
ifvirtualenv_version!=cfg['version_info']:
return (
f'virtualenv python version did not match created version:\n'
f'- actual version:{virtualenv_version}\n'
f'- expected version:{cfg["version_info"]}\n'
)
# made with an older version of virtualenv? skip `base-executable` check
if'base-executable'notincfg:
returnNone
base_exe_version=_version_info(cfg['base-executable'])
ifbase_exe_version!=cfg['version_info']:
return (
f'base executable python version does not match created version:\n'
f'- base-executable version:{base_exe_version}\n'
f'- expected version:{cfg["version_info"]}\n'
)
else:
returnNone
definstall_environment(
prefix:Prefix,
version:str,
additional_dependencies:Sequence[str],
)->None:
envdir=lang_base.environment_dir(prefix,ENVIRONMENT_DIR,version)
venv_cmd= [sys.executable,'-mvirtualenv',envdir]
python=norm_version(version)
ifpythonisnotNone:
venv_cmd.extend(('-p',python))
install_cmd= ('python','-mpip','install','.',*additional_dependencies)
cmd_output_b(*venv_cmd,cwd='/')
within_env(prefix,version):
lang_base.setup_cmd(prefix,install_cmd)