@@ -86,14 +86,15 @@ class Env(object):
86
86
>>> Env().pack(output="environment.tar.gz")
87
87
"/full/path/to/environment.tar.gz"
88
88
"""
89
- __slots__ = ('_context' , 'files' , '_excluded_files' )
89
+ __slots__ = ('_context' , 'files' , '_excluded_files' , '_base_env' )
90
90
91
91
def __init__ (self , prefix = None ):
92
92
context , files = load_environment (prefix )
93
93
94
94
self ._context = context
95
95
self .files = files
96
96
self ._excluded_files = []
97
+ self ._base_env = None
97
98
98
99
def _copy_with_files (self , files , excluded_files ):
99
100
out = object .__new__ (Env )
@@ -102,6 +103,13 @@ def _copy_with_files(self, files, excluded_files):
102
103
out ._excluded_files = excluded_files
103
104
return out
104
105
106
+ @property
107
+ def base_env (self ):
108
+ if self ._base_env is None \
109
+ and self .kind in ("virtualenv" , "venv" ):
110
+ self ._base_env = Env (self .orig_prefix )
111
+ return self ._base_env
112
+
105
113
@property
106
114
def prefix (self ):
107
115
return self ._context .prefix
@@ -224,7 +232,7 @@ def _output_and_format(self, output=None, format='infer'):
224
232
225
233
def pack (self , output = None , format = 'infer' , python_prefix = None ,
226
234
verbose = False , force = False , compress_level = 4 , zip_symlinks = False ,
227
- zip_64 = True ):
235
+ zip_64 = True , standalone = False ):
228
236
"""Package the virtual environment into an archive file.
229
237
230
238
Parameters
@@ -259,6 +267,10 @@ def pack(self, output=None, format='infer', python_prefix=None,
259
267
symlinks*. Default is False. Ignored if format isn't ``zip``.
260
268
zip_64 : bool, optional
261
269
Whether to enable ZIP64 extensions. Default is True.
270
+ standalone : bool, optional
271
+ If True include the files from the base Python environment in the
272
+ package and create a standalone Python environment that does not
273
+ need the base environment when unpacked.
262
274
263
275
Returns
264
276
-------
@@ -274,6 +286,10 @@ def pack(self, output=None, format='infer', python_prefix=None,
274
286
if verbose :
275
287
print ("Packing environment at %r to %r" % (self .prefix , output ))
276
288
289
+ all_files = list (self .files )
290
+ if standalone :
291
+ all_files = combine_env_files (self , self .base_env )
292
+
277
293
fd , temp_path = tempfile .mkstemp ()
278
294
279
295
try :
@@ -282,8 +298,8 @@ def pack(self, output=None, format='infer', python_prefix=None,
282
298
compress_level = compress_level ,
283
299
zip_symlinks = zip_symlinks ,
284
300
zip_64 = zip_64 ) as arc :
285
- packer = Packer (self ._context , arc , python_prefix )
286
- with progressbar (self . files , enabled = verbose ) as files :
301
+ packer = Packer (self ._context , arc , python_prefix , standalone = standalone )
302
+ with progressbar (all_files , enabled = verbose ) as files :
287
303
try :
288
304
for f in files :
289
305
packer .add (f )
@@ -323,7 +339,7 @@ class File(namedtuple('File', ('source', 'target'))):
323
339
324
340
def pack (prefix = None , output = None , format = 'infer' , python_prefix = None ,
325
341
verbose = False , force = False , compress_level = 4 , zip_symlinks = False ,
326
- zip_64 = True , filters = None ):
342
+ zip_64 = True , filters = None , standalone = False ):
327
343
"""Package an existing virtual environment into an archive file.
328
344
329
345
Parameters
@@ -365,6 +381,10 @@ def pack(prefix=None, output=None, format='infer', python_prefix=None,
365
381
``(kind, pattern)``, where ``kind`` is either ``'exclude'`` or
366
382
``'include'`` and ``pattern`` is a file pattern. Filters are applied in
367
383
the order specified.
384
+ standalone : bool, optional
385
+ If True include the files from the base Python environment in the
386
+ package and create a standalone Python environment that does not
387
+ need the base environment when unpacked.
368
388
369
389
Returns
370
390
-------
@@ -389,7 +409,8 @@ def pack(prefix=None, output=None, format='infer', python_prefix=None,
389
409
python_prefix = python_prefix ,
390
410
verbose = verbose , force = force ,
391
411
compress_level = compress_level ,
392
- zip_symlinks = zip_symlinks , zip_64 = zip_64 )
412
+ zip_symlinks = zip_symlinks , zip_64 = zip_64 ,
413
+ standalone = standalone )
393
414
394
415
395
416
def check_prefix (prefix = None ):
@@ -404,7 +425,7 @@ def check_prefix(prefix=None):
404
425
if not os .path .exists (prefix ):
405
426
raise VenvPackException ("Environment path %r doesn't exist" % prefix )
406
427
407
- for check in [check_venv , check_virtualenv ]:
428
+ for check in [check_venv , check_virtualenv , check_baseenv ]:
408
429
try :
409
430
return check (prefix )
410
431
except VenvPackException :
@@ -460,6 +481,22 @@ def check_virtualenv(prefix):
460
481
461
482
return context
462
483
484
+ def check_baseenv (prefix ):
485
+ python_lib , python_include = find_python_lib_include (prefix )
486
+
487
+ if not os .path .exists (os .path .join (prefix , python_lib , "os.py" )):
488
+ raise VenvPackException ("%r is not a valid Python environment" % prefix )
489
+
490
+
491
+ context = AttrDict ()
492
+
493
+ context .kind = 'base'
494
+ context .prefix = prefix
495
+ context .orig_prefix = None
496
+ context .py_lib = python_lib
497
+ context .py_include = python_include
498
+
499
+ return context
463
500
464
501
def find_python_lib_include (prefix ):
465
502
if on_win :
@@ -513,10 +550,13 @@ def load_environment(prefix):
513
550
514
551
# Files to ignore
515
552
remove = {join (BIN_DIR , "activate" + suffix ) for suffix in ('' , '.csh' , '.fish' , '.bat' , '.ps1' )}
553
+ for script in ("activate" , "Activate" ):
554
+ for suffix in ('' , '.csh' , '.fish' , '.bat' , '.ps1' ):
555
+ remove .add (join (BIN_DIR , script + suffix ))
516
556
517
557
if context .kind == 'virtualenv' :
518
558
remove .add (join (context .py_lib , 'orig-prefix.txt' ))
519
- else :
559
+ elif context . kind == 'venv' :
520
560
remove .add ('pyvenv.cfg' )
521
561
522
562
res = []
@@ -659,11 +699,44 @@ def check_python_prefix(python_prefix, context):
659
699
return python_prefix , rewrites
660
700
661
701
702
+ def combine_env_files (venv , base_env ):
703
+ """Combines the files from a virtual environment and it's base environment.
704
+ Returns a new list of files.
705
+ """
706
+ all_files = {}
707
+ exe_suffix = '.exe' if on_win else ''
708
+
709
+ # Don't include the Python executables from the virtual environment
710
+ exclude = {
711
+ os .path .join (venv .prefix , BIN_DIR , 'python' + exe_suffix ).lower (),
712
+ os .path .join (venv .prefix , BIN_DIR , 'pythonw' + exe_suffix ).lower ()
713
+ }
714
+
715
+ # Copy the base Python excectuables and shared libraries into BIN_DIR as the
716
+ # other scripts need them to be there for them to work.
717
+ for file in os .listdir (base_env .prefix ):
718
+ source = os .path .join (base_env .prefix , file )
719
+ if os .path .isfile (source ):
720
+ _ , ext = os .path .splitext (file )
721
+ if ext .lower () in ("" , ".dll" , ".so" , ".exe" , ".pdb" ):
722
+ target = os .path .join (BIN_DIR , file )
723
+ all_files [target ] = File (source , target )
724
+ exclude .add (source .lower ())
725
+
726
+ # Include all files from the base env, and for any files that exist in both use the
727
+ # file from the virtual env.
728
+ all_files .update ({f .target : f for f in base_env .files if f .source .lower () not in exclude })
729
+ all_files .update ({f .target : f for f in venv .files if f .source .lower () not in exclude })
730
+
731
+ return all_files .values ()
732
+
733
+
662
734
class Packer (object ):
663
- def __init__ (self , context , archive , python_prefix ):
735
+ def __init__ (self , context , archive , python_prefix , standalone = False ):
664
736
self .context = context
665
737
self .prefix = context .prefix
666
738
self .archive = archive
739
+ self .standalone = standalone
667
740
668
741
python_prefix , rewrites = check_python_prefix (python_prefix , context )
669
742
self .python_prefix = python_prefix
@@ -689,34 +762,35 @@ def add(self, file):
689
762
self .archive .add (file .source , file .target )
690
763
691
764
def finish (self ):
692
- script_dirs = ['common' , 'nt' ]
693
-
694
- for d in script_dirs :
695
- dirpath = os .path .join (SCRIPTS , d )
696
- for f in os .listdir (dirpath ):
697
- source = os .path .join (dirpath , f )
698
- target = os .path .join (BIN_DIR , f )
699
- self .archive .add (source , target )
700
-
701
- if self .context .kind == 'venv' :
702
- pyvenv_cfg = os .path .join (self .prefix , 'pyvenv.cfg' )
703
- if self .python_prefix is None :
704
- self .archive .add (pyvenv_cfg , 'pyvenv.cfg' )
705
- else :
706
- with open (pyvenv_cfg ) as fil :
707
- data = fil .read ()
708
- data = data .replace (self .context .orig_prefix ,
709
- self .python_prefix )
710
- self .archive .add_bytes (pyvenv_cfg , data .encode (), 'pyvenv.cfg' )
711
- else :
712
- origprefix_txt = os .path .join (self .context .prefix ,
713
- self .context .py_lib ,
714
- 'orig-prefix.txt' )
715
- target = os .path .relpath (origprefix_txt , self .prefix )
716
-
717
- if self .python_prefix is None :
718
- self .archive .add (origprefix_txt , target )
765
+ if not self .standalone :
766
+ script_dirs = ['common' , 'nt' ]
767
+
768
+ for d in script_dirs :
769
+ dirpath = os .path .join (SCRIPTS , d )
770
+ for f in os .listdir (dirpath ):
771
+ source = os .path .join (dirpath , f )
772
+ target = os .path .join (BIN_DIR , f )
773
+ self .archive .add (source , target )
774
+
775
+ if self .context .kind == 'venv' :
776
+ pyvenv_cfg = os .path .join (self .prefix , 'pyvenv.cfg' )
777
+ if self .python_prefix is None :
778
+ self .archive .add (pyvenv_cfg , 'pyvenv.cfg' )
779
+ else :
780
+ with open (pyvenv_cfg ) as fil :
781
+ data = fil .read ()
782
+ data = data .replace (self .context .orig_prefix ,
783
+ self .python_prefix )
784
+ self .archive .add_bytes (pyvenv_cfg , data .encode (), 'pyvenv.cfg' )
719
785
else :
720
- self .archive .add_bytes (origprefix_txt ,
721
- self .python_prefix .encode (),
722
- target )
786
+ origprefix_txt = os .path .join (self .context .prefix ,
787
+ self .context .py_lib ,
788
+ 'orig-prefix.txt' )
789
+ target = os .path .relpath (origprefix_txt , self .prefix )
790
+
791
+ if self .python_prefix is None :
792
+ self .archive .add (origprefix_txt , target )
793
+ else :
794
+ self .archive .add_bytes (origprefix_txt ,
795
+ self .python_prefix .encode (),
796
+ target )
0 commit comments