22
22
os .environ ["HOME" ] = os .environ ["USERPROFILE" ]
23
23
24
24
class Package :
25
- "standardize a Package from filename or pip list"
26
- def __init__ (self , fname , suggested_summary = None ):
25
+ """Standardize a Package from filename or pip list."" "
26
+ def __init__ (self , fname : str , suggested_summary : str = None ):
27
27
self .fname = fname
28
28
self .description = piptree .sum_up (suggested_summary ) if suggested_summary else ""
29
29
self .name , self .version = None , None
@@ -40,33 +40,32 @@ def __str__(self):
40
40
41
41
42
42
class Distribution :
43
- def __init__ ( self , target = None , verbose = False ):
44
- # if no target path given, take the current python interpreter one
45
- self .target = target or os .path .dirname (sys .executable )
43
+ """Handles operations on a WinPython distribution."""
44
+ def __init__ ( self , target : str = None , verbose : bool = False ):
45
+ self .target = target or os .path .dirname (sys .executable ) # Default target more explicit
46
46
self .verbose = verbose
47
47
self .pip = None
48
- self .to_be_removed = [] # list of directories to be removed later
49
- self .version , self .architecture = utils .get_python_infos (target )
50
- # name of the exe (python.exe or pypy3.exe)
48
+ self .to_be_removed = []
49
+ self .version , self .architecture = utils .get_python_infos (self .target )
51
50
self .short_exe = Path (utils .get_python_executable (self .target )).name
52
51
53
52
def clean_up (self ):
54
- """Remove directories which couldn't be removed when building """
53
+ """Remove directories that were marked for removal. """
55
54
for path in self .to_be_removed :
56
55
try :
57
56
shutil .rmtree (path , onexc = utils .onerror )
58
- except WindowsError :
59
- print (f"Directory { path } could not be removed " , file = sys .stderr )
57
+ except OSError as e :
58
+ print (f"Error: Could not remove directory { path } : { e } " , file = sys .stderr )
60
59
61
- def remove_directory (self , path ):
62
- """Try to remove directory -- on WindowsError, remove it later """
60
+ def remove_directory (self , path : str ):
61
+ """Try to remove a directory, add to removal list on failure. """
63
62
try :
64
63
shutil .rmtree (path )
65
- except WindowsError :
64
+ except OSError :
66
65
self .to_be_removed .append (path )
67
66
68
- def copy_files (self , package , targetdir , srcdir , dstdir , create_bat_files = False ):
69
- """Add copy task """
67
+ def copy_files (self , package : Package , targetdir : str , srcdir : str , dstdir : str , create_bat_files : bool = False ):
68
+ """Copy files from srcdir to dstdir within the target distribution. """
70
69
srcdir = str (Path (targetdir ) / srcdir )
71
70
if not Path (srcdir ).is_dir ():
72
71
return
@@ -112,8 +111,8 @@ def create_file(self, package, name, dstdir, contents):
112
111
fd .write (contents )
113
112
package .files .append (dst )
114
113
115
- def get_installed_packages (self , update = False ):
116
- """Return installed packages"""
114
+ def get_installed_packages (self , update : bool = False ) -> list [ Package ] :
115
+ """Return installed packages. """
117
116
118
117
# Include package installed via pip (not via WPPM)
119
118
wppm = []
@@ -133,14 +132,14 @@ def get_installed_packages(self, update=False):
133
132
]
134
133
return sorted (wppm , key = lambda tup : tup .name .lower ())
135
134
136
- def find_package (self , name ) :
137
- """Find installed package"""
135
+ def find_package (self , name : str ) -> Package | None :
136
+ """Find installed package by name. """
138
137
for pack in self .get_installed_packages ():
139
138
if utils .normalize (pack .name ) == utils .normalize (name ):
140
139
return pack
141
140
142
- def patch_all_shebang (self , to_movable = True , max_exe_size = 999999 , targetdir = "" ):
143
- """make all python launchers relatives """
141
+ def patch_all_shebang (self , to_movable : bool = True , max_exe_size : int = 999999 , targetdir : str = "" ):
142
+ """Make all python launchers relative. """
144
143
import glob
145
144
146
145
for ffname in glob .glob (r"%s\Scripts\*.exe" % self .target ):
@@ -150,10 +149,9 @@ def patch_all_shebang(self, to_movable=True, max_exe_size=999999, targetdir=""):
150
149
for ffname in glob .glob (r"%s\Scripts\*.py" % self .target ):
151
150
utils .patch_shebang_line_py (ffname , to_movable = to_movable , targetdir = targetdir )
152
151
153
- def install (self , package , install_options = None ):
154
- """Install package in distribution"""
155
- # wheel addition
156
- if package .fname .endswith ((".whl" , ".tar.gz" , ".zip" )):
152 + def install (self , package : Package , install_options : list [str ] = None ): # Type hint install_options
153
+ """Install package in distribution."""
154
+ if package .fname .endswith ((".whl" , ".tar.gz" , ".zip" )): # Check extension with tuple
157
155
self .install_bdist_direct (package , install_options = install_options )
158
156
self .handle_specific_packages (package )
159
157
# minimal post-install actions
@@ -206,9 +204,7 @@ def patch_standard_packages(self, package_name="", to_movable=True):
206
204
# sheb_mov2 = tried way, but doesn't work for pip (at least)
207
205
sheb_fix = " executable = get_executable()"
208
206
sheb_mov1 = " executable = os.path.join(os.path.basename(get_executable()))"
209
- sheb_mov2 = (
210
- " executable = os.path.join('..',os.path.basename(get_executable()))"
211
- )
207
+ sheb_mov2 = " executable = os.path.join('..',os.path.basename(get_executable()))"
212
208
213
209
# Adpating to PyPy
214
210
the_place = site_package_place + r"pip\_vendor\distlib\scripts.py"
@@ -240,30 +236,25 @@ def patch_standard_packages(self, package_name="", to_movable=True):
240
236
else :
241
237
self .create_pybat (package_name .lower ())
242
238
243
- def create_pybat (
244
- self ,
245
- names = "" ,
246
- contents = r"""@echo off
239
+
240
+ def create_pybat (self , names = "" , contents = r"""@echo off
247
241
..\python "%~dpn0" %*""" ,
248
242
):
249
243
"""Create launcher batch script when missing"""
250
244
251
- scriptpy = str (Path (self .target ) / "Scripts" ) # std Scripts of python
252
-
253
- # PyPy has no initial Scipts directory
254
- if not Path (scriptpy ).is_dir ():
255
- os .mkdir (scriptpy )
245
+ scriptpy = Path (self .target ) / "Scripts" # std Scripts of python
246
+ os .makedirs (scriptpy , exist_ok = True )
256
247
if not list (names ) == names :
257
248
my_list = [f for f in os .listdir (scriptpy ) if "." not in f and f .startswith (names )]
258
249
else :
259
250
my_list = names
260
251
for name in my_list :
261
- if Path ( scriptpy) .is_dir () and (Path ( scriptpy ) / name ).is_file ():
252
+ if scriptpy .is_dir () and (scriptpy / name ).is_file ():
262
253
if (
263
- not (Path ( scriptpy ) / (name + ".exe" )).is_file ()
264
- and not (Path ( scriptpy ) / (name + ".bat" )).is_file ()
254
+ not (scriptpy / (name + ".exe" )).is_file ()
255
+ and not (scriptpy / (name + ".bat" )).is_file ()
265
256
):
266
- with open (Path ( scriptpy ) / (name + ".bat" ), "w" ) as fd :
257
+ with open (scriptpy / (name + ".bat" ), "w" ) as fd :
267
258
fd .write (contents )
268
259
fd .close ()
269
260
@@ -272,9 +263,7 @@ def handle_specific_packages(self, package):
272
263
if package .name .lower () in ("pyqt4" , "pyqt5" , "pyside2" ):
273
264
# Qt configuration file (where to find Qt)
274
265
name = "qt.conf"
275
- contents = """[Paths]
276
- Prefix = .
277
- Binaries = ."""
266
+ contents = """[Paths]\n Prefix = .\n Binaries = ."""
278
267
self .create_file (package , name , str (Path ("Lib" ) / "site-packages" / package .name ), contents )
279
268
self .create_file (package , name , "." , contents .replace ("." , f"./Lib/site-packages/{ package .name } " ))
280
269
# pyuic script
@@ -296,13 +285,14 @@ def handle_specific_packages(self, package):
296
285
for dirname in ("Loader" , "port_v2" , "port_v3" ):
297
286
self .create_file (package , "__init__.py" , str (Path (uic_path ) / dirname ), "" )
298
287
299
- def _print (self , package , action ):
300
- """Print package-related action text (e.g. 'Installing')"""
288
+
289
+ def _print (self , package : Package , action : str ):
290
+ """Print package-related action text."""
301
291
text = f"{ action } { package .name } { package .version } "
302
292
if self .verbose :
303
293
utils .print_box (text )
304
294
else :
305
- print (" " + text + " ..." , end = " " )
295
+ print (f " { text } ..." , end = " " )
306
296
307
297
def _print_done (self ):
308
298
"""Print OK at the end of a process"""
@@ -318,6 +308,7 @@ def uninstall(self, package):
318
308
subprocess .call ([this_exec , "-m" , "pip" , "uninstall" , package .name , "-y" ], cwd = self .target )
319
309
self ._print_done ()
320
310
311
+
321
312
def install_bdist_direct (self , package , install_options = None ):
322
313
"""Install a package directly !"""
323
314
self ._print (package ,f"Installing { package .fname .split ('.' )[- 1 ]} " )
@@ -335,18 +326,22 @@ def install_bdist_direct(self, package, install_options=None):
335
326
package = Package (fname )
336
327
self ._print_done ()
337
328
338
- def install_script (self , script , install_options = None ):
329
+
330
+ def install_script (self , script : str , install_options : list [str ] = None ): # Type hint install_options
331
+ """Install a script using pip."""
339
332
try :
340
333
fname = utils .do_script (
341
334
script ,
342
335
python_exe = utils .get_python_executable (self .target ), # PyPy3 !
343
336
verbose = self .verbose ,
344
337
install_options = install_options ,
345
338
)
346
- except RuntimeError :
339
+ except RuntimeError as e : # Catch specific RuntimeError
347
340
if not self .verbose :
348
341
print ("Failed!" )
349
- raise
342
+ raise # Re-raise if not verbose
343
+ else :
344
+ print (f"Script installation failed: { e } " ) # Print error if verbose
350
345
351
346
352
347
def main (test = False ):
@@ -415,7 +410,7 @@ def main(test=False):
415
410
const = True ,
416
411
default = False ,
417
412
help = f"list packages matching the given [optionnal] package expression: wppm -ls, wppm -ls pand" ,
418
- )
413
+ )
419
414
parser .add_argument (
420
415
"-p" ,
421
416
dest = "pipdown" ,
@@ -473,9 +468,8 @@ def main(test=False):
473
468
)
474
469
args = parser .parse_args ()
475
470
targetpython = None
476
- if args .target and not args .target == sys .prefix :
477
- targetpython = args .target if args .target [- 4 :] == '.exe' else str (Path (args .target ) / 'python.exe' )
478
- # print(targetpython.resolve() to check)
471
+ if args .target and args .target != sys .prefix :
472
+ targetpython = args .target if args .target .lower ().endswith ('.exe' ) else str (Path (args .target ) / 'python.exe' )
479
473
if args .install and args .uninstall :
480
474
raise RuntimeError ("Incompatible arguments: --install and --uninstall" )
481
475
if args .registerWinPython and args .unregisterWinPython :
@@ -492,51 +486,49 @@ def main(test=False):
492
486
sys .exit ()
493
487
elif args .list :
494
488
pip = piptree .PipData (targetpython )
495
- todo = [l for l in pip .pip_list (full = True ) if bool (re .search (args .fname , l [0 ])) ]
496
- titles = [['Package' , 'Version' , 'Summary' ],['_' * max (x , 6 ) for x in utils .columns_width (todo )]]
489
+ todo = [l for l in pip .pip_list (full = True ) if bool (re .search (args .fname , l [0 ]))]
490
+ titles = [['Package' , 'Version' , 'Summary' ], ['_' * max (x , 6 ) for x in utils .columns_width (todo )]]
497
491
listed = utils .formatted_list (titles + todo , max_width = 70 )
498
492
for p in listed :
499
493
print (* p )
500
494
sys .exit ()
501
495
elif args .all :
502
496
pip = piptree .PipData (targetpython )
503
- todo = [l for l in pip .pip_list (full = True ) if bool (re .search (args .fname , l [0 ])) ]
497
+ todo = [l for l in pip .pip_list (full = True ) if bool (re .search (args .fname , l [0 ]))]
504
498
for l in todo :
505
499
# print(pip.distro[l[0]])
506
500
title = f"** Package: { l [0 ]} **"
507
- print ("\n " + "*" * len (title ), f"\n { title } " , "\n " + "*" * len (title ) )
501
+ print ("\n " + "*" * len (title ), f"\n { title } " , "\n " + "*" * len (title ))
508
502
for key , value in pip .raw [l [0 ]].items ():
509
503
rawtext = json .dumps (value , indent = 2 , ensure_ascii = False )
510
504
lines = [l for l in rawtext .split (r"\n" ) if len (l .strip ()) > 2 ]
511
- if key .lower () != 'description' or args .verbose == True :
505
+ if key .lower () != 'description' or args .verbose :
512
506
print (f"{ key } : " , "\n " .join (lines ).replace ('"' , "" ))
513
- sys .exit ()
507
+ sys .exit ()
514
508
if args .registerWinPython :
515
509
print (registerWinPythonHelp )
516
510
if utils .is_python_distribution (args .target ):
517
511
dist = Distribution (args .target )
518
512
else :
519
- raise WindowsError (f"Invalid Python distribution { args .target } " )
513
+ raise OSError (f"Invalid Python distribution { args .target } " )
520
514
print (f"registering { args .target } " )
521
515
print ("continue ? Y/N" )
522
516
theAnswer = input ()
523
517
if theAnswer == "Y" :
524
518
from winpython import associate
525
-
526
519
associate .register (dist .target , verbose = args .verbose )
527
520
sys .exit ()
528
521
if args .unregisterWinPython :
529
522
print (unregisterWinPythonHelp )
530
523
if utils .is_python_distribution (args .target ):
531
524
dist = Distribution (args .target )
532
525
else :
533
- raise WindowsError (f"Invalid Python distribution { args .target } " )
526
+ raise OSError (f"Invalid Python distribution { args .target } " )
534
527
print (f"unregistering { args .target } " )
535
528
print ("continue ? Y/N" )
536
529
theAnswer = input ()
537
530
if theAnswer == "Y" :
538
531
from winpython import associate
539
-
540
532
associate .unregister (dist .target , verbose = args .verbose )
541
533
sys .exit ()
542
534
elif not args .install and not args .uninstall :
@@ -546,7 +538,7 @@ def main(test=False):
546
538
parser .print_help ()
547
539
sys .exit ()
548
540
else :
549
- raise IOError (f"File not found: { args .fname } " )
541
+ raise FileNotFoundError (f"File not found: { args .fname } " )
550
542
if utils .is_python_distribution (args .target ):
551
543
dist = Distribution (args .target , verbose = True )
552
544
try :
@@ -560,7 +552,7 @@ def main(test=False):
560
552
except NotImplementedError :
561
553
raise RuntimeError ("Package is not (yet) supported by WPPM" )
562
554
else :
563
- raise WindowsError (f"Invalid Python distribution { args .target } " )
555
+ raise OSError (f"Invalid Python distribution { args .target } " )
564
556
565
557
566
558
if __name__ == "__main__" :
0 commit comments