pyenv-virtualenv for Windows 1.2
A 'pyenv' plugin to manage Python virtual environments, depending on different Python versions, for various Python projects.
Loading...
Searching...
No Matches
pyenv-virtualenv-init.py
Go to the documentation of this file.
1##
2# @package pyenv-virtualenv-init
3# @file pyenv-virtualenv-init.py
4# @author Michael Paul Korthals
5# @date 2025-07-10
6# @version 1.0.0
7# @copyright © 2025 Michael Paul Korthals. All rights reserved.
8# See License details in the documentation.
9#
10# Utility to reconfigure 'pyenv-virtualenv' for Windows
11# after upgrading 'pyenv'
12#
13
14# --- IMPORTS ----------------------------------------------------------
15
16# Python
17import argparse
18import os
19import subprocess
20import sys
21
22# Avoid colored output problems
23os.system('')
24
25# Community
26try:
27 import virtualenv
28except ImportError():
29 print(
30 '\x1b[101mCRITICAL %s\x1b[0m'
31 %
32 'Cannot find package "%s".'
33 %
34 'virtualenv'
35 )
36 print(
37 '\x1b[37mINFO %s\x1b[0m'
38 %
39 'Install it using "pip". Then try again.')
40 import virtualenv
41
42# My
43import lib.hlp as hlp
44import lib.log as log
45
46
47# --- RUN ---------------------------------------------------------------
48
49# noinspection PyUnusedLocal
50
51## Sub routine to run the application.
52#
53# @param args Parsed command line arguments of this application.
54# (e.g. for CMD or PowerShell).
55# @return RC = 0 or other values in case of error.
56def run(args: argparse.Namespace) -> int:
57 rc: int = 0
58 # noinspection PyBroadException
59 try:
60 while True:
61 corrected: bool = False
62 log.info('Checking operation environment.')
63 # Check if path to 'shims' directory of the plugin has
64 # higher priority than 'bin' and 'shims' folders of 'pyenv'.
65 log.verbose('Checking PYENV_ROOT ...')
66 if not 'PYENV_ROOT' in os.environ:
67 log.error('Cannot find "PYENV_ROOT" in the environment variables.')
68 log.info('Possibly "pyenv" for Windows has been uninstalled or damaged.')
69 log.info('Possibly you need to install the newest versions of "pyenv" and "pyenv-virtualenv" for Windows.')
70 break
71 log.verbose('"PYENV_ROOT" environment variable exists.')
72 pyenv_root_dir = os.environ['PYENV_ROOT'].strip()
73 log.verbose(f'Checking existence of directory "{pyenv_root_dir}" ...')
74 if not os.path.isdir(pyenv_root_dir):
75 log.error(f'Cannot find directory "{pyenv_root_dir}".')
76 log.info('Possibly "pyenv" for Windows has been uninstalled or damaged.')
77 log.info('Possibly you need to install the newest versions of "pyenv" and "pyenv-virtualenv" for Windows.')
78 break
79 log.verbose(f'Directory "{pyenv_root_dir}" exists.')
80 log.verbose('Managing PATH priorities ...')
81 pve_path1 = os.path.join(
82 pyenv_root_dir,
83 'plugins',
84 'pyenv-virtualenv',
85 'shims'
86 )
87 pve_index = []
88 pve_paths = []
89 paths = os.environ['PATH'].split(';')
90 pev_path1 = os.path.join(
91 pyenv_root_dir,
92 'bin'
93 )
94 pev_path2 = os.path.join(
95 pyenv_root_dir,
96 'shims'
97 )
98 pev_index = []
99 pev_paths = []
100 for i in range(len(paths)):
101 path = paths[i]
102 path = path.strip()
103 if not os.path.isdir(path):
104 log.warning(f'Directory "{path}" in PATH is not available.')
105 log.info(f'Please manually correct this deviation afterward, if necessary.')
106 if path == pve_path1:
107 pve_index.append(i)
108 pve_paths.append(path)
109 if path == pev_path1:
110 pev_index.append(i)
111 pev_paths.append(path)
112 if path == pev_path2:
113 pev_index.append(i)
114 pev_paths.append(path)
115 # End for
116 if len(pev_index) == 0:
117 log.error('Cannot recognize "pyenv" in PATH.')
118 log.info('Possibly "pyenv" for Windows has been uninstalled or damaged.')
119 log.info('Possibly you need to install the newest versions of "pyenv" and "pyenv-virtualenv" for Windows.')
120 break
121 if len(pve_paths) > 1:
122 # Auto-remove multiple 'pyenv-virtualenv' 'shims'
123 # entries. There must only be one.
124 # The one with the highest priority.
125 for j in reversed(range(len(pve_index[1:]))):
126 removal_index = pve_index[j]
127 path = paths.pop(removal_index)
128 log.warning(f'Obsolete clone of path "{path}" found in PATH.')
129 log.info(f'This entry has been automatically removed.')
130 # Taking account of this removal,
131 # correct the 'pyenv' PATH indices.
132 for k in range(len(pev_index)):
133 index = pev_index[k]
134 if index < removal_index:
135 pev_index[k] -= 1
136 # End if
137 # End for
138 # End for
139 pve_index = pve_index[0:1]
140 # End if
141 min_pev_index = min(pev_index)
142 if len(pve_paths) == 0:
143 corrected = True
144 # Prepend the missing 'shims' directory to PATH
145 cmd = [
146 'setx',
147 'PATH',
148 '{};{}'.format(pve_path1, os.environ['PATH']),
149 '/m'
150 ]
151 log.verbose(f'Execute: {cmd}')
152 cp = subprocess.run(
153 cmd,
154 shell=True
155 )
156 rc = cp.returncode
157 if rc != 0:
158 log.error(f'Cannot permanently prepend "{pve_path1}" to the PATH environment variable. (RC = {rc}.')
159 log.info('Open new console terminal as "Administrator", in which you want to try again.')
160 break
161 elif min_pev_index < pve_index[0]:
162 corrected = True
163 # Remove the existing 'shims"
164 paths.pop(pve_index[0])
165 # Prepend the missing 'shims' directory to PATH.
166 # This command needs 'Administrator' privileges.
167 cmd = [
168 'setx',
169 'PATH',
170 '{};{}'.format(pve_path1, os.environ['PATH']),
171 '/m'
172 ]
173 log.verbose(f'Execute: {cmd}')
174 cp = subprocess.run(
175 cmd,
176 shell=True
177 )
178 rc = cp.returncode
179 if rc != 0:
180 log.error(f'Cannot permanently prepend "{pve_path1}" to the PATH environment variable. (RC = {rc}.')
181 log.info('Open new console terminal as "Administrator", in which you want to try again.')
182 break
183 # End if
184 # Endif
185 log.verbose('PATH priorities managed.')
186 if corrected:
187 log.success(f'Operation environment checked and corrected.')
188 else:
189 log.success(f'Operation environment checked.')
190 log.success(f'"pyenv-virtualenv" should work as expected.')
191 # Go on
192 break
193 # End while
194 except:
195 log.error(sys.exc_info())
196 rc = 1
197 return rc
198
199
200# --- MAIN --------------------------------------------------------------
201
202## Parse CLI arguments for this application.<br>
203# <br>
204# Implement this as required, but don't touch the interface definition
205# for input and output.
206#
207# @return A tuple of:
208# * Namespace to read arguments in "dot" notation or None
209# in case of help or error.
210# * RC = 0 or another value in case of error.
211def parseCliArguments() -> tuple[(argparse.Namespace, None), int]:
212 rc: int = 0
213 # noinspection PyBroadException
214 try:
215 parser = argparse.ArgumentParser(
216# --- BEGIN CHANGE -----------------------------------------------------
217 prog='pyenv virtualenv-init',
218 description='Initialize the terminal shell to work on Python virtual environment in "pyenv".'
219 )
220 # Add optional argument
221 parser.add_argument(
222 '-s', '--shell',
223 dest='shell',
224 type=str,
225 default='',
226 help='Command string to call a shell command or batch in the shell like you would prefer. Default: "%%COMSPEC%% /K" = Windows CMD.'
227 )
228# --- END CHANGE -------------------------------------------------------
229 return parser.parse_args(), rc
230 except SystemExit:
231 return None, 0 # -h, --help
232 except:
233 log.error(sys.exc_info())
234 return None, 1
235
236## Main routine of the application.
237#
238# @return RC = 0 or other values in case of error.
239def main() -> int:
240 # noinspection PyBroadException
241 try:
242 while True:
243 # Audit the operating system platform
244 rc = hlp.auditPlatform('Windows')
245 if rc != 0:
246 # Deviation: Reject unsupported platform
247 break
248 # Audit the global Python version number
250 if rc != 0:
251 # Deviation: Reject unsupported Python version
252 break
253 # Initialize the colored logging to console
255 # Audit the "pyenv" version number
256 rc = hlp.auditPyEnv('3')
257 if rc != 0:
258 # Deviation: Reject unsupported "pyenv" version
259 break
260 # Parse arguments
261 log.verbose('Parsing arguments ...')
262 args, rc = parseCliArguments()
263 if rc != 0:
264 break
265 if args is None: # -h, --help
266 break
267 # Run this application
268 log.verbose('Running application ...')
269 rc = run(args)
270 if rc != 0:
271 break
272 # Go on
273 break
274 # End while
275 except Exception as exc:
277 log.error(sys.exc_info())
278 else:
279 print(
280 '\x1b[91mERROR: Unexpected error "%s".\x1b[0m'
281 %
282 str(exc)
283 )
284 rc = 1
285 return rc
286
287
288if __name__ == "__main__":
289 sys.exit(main())
290
291# --- END OF CODE ------------------------------------------------------
292
int auditPyEnv(str min_ver)
Check if "pyenv" version is greater or equal the given minimal version.
Definition hlp.py:213
int auditGlobalPythonVersion(str min_ver)
Check if Python version is greater or equal the given minimal version.
Definition hlp.py:148
int auditPlatform(str name)
Check if the program in running on the required platform.
Definition hlp.py:85
warning((str, tuple) msg)
Log warning message colored to console only.
Definition log.py:191
verbose((str, tuple) msg)
Log verbose message colored to console only.
Definition log.py:209
initLogging()
Initialize the logging.
Definition log.py:71
success((str, tuple) msg)
Log success message colored to console only.
Definition log.py:185
bool isInitialized()
Definition log.py:90
info((str, tuple) msg)
Log info message colored to console only.
Definition log.py:203
error((str, tuple) msg)
Log error message colored to console only.
Definition log.py:179
tuple[(argparse.Namespace, None), int] parseCliArguments()
Parse CLI arguments for this application.
int main()
Main routine of the application.
int run(argparse.Namespace args)
Sub routine to run the application.