comparison env/lib/python3.7/site-packages/virtualenv/create/creator.py @ 2:6af9afd405e9 draft

"planemo upload commit 0a63dd5f4d38a1f6944587f52a8cd79874177fc1"
author shellac
date Thu, 14 May 2020 14:56:58 -0400
parents 26e78fe6e8c4
children
comparison
equal deleted inserted replaced
1:75ca89e9b81c 2:6af9afd405e9
1 from __future__ import absolute_import, print_function, unicode_literals
2
3 import json
4 import logging
5 import os
6 import sys
7 from abc import ABCMeta, abstractmethod
8 from argparse import ArgumentTypeError
9 from ast import literal_eval
10 from collections import OrderedDict
11
12 from six import add_metaclass
13
14 from virtualenv.discovery.cached_py_info import LogCmd
15 from virtualenv.info import WIN_CPYTHON_2
16 from virtualenv.pyenv_cfg import PyEnvCfg
17 from virtualenv.util.path import Path, safe_delete
18 from virtualenv.util.six import ensure_str, ensure_text
19 from virtualenv.util.subprocess import run_cmd
20 from virtualenv.util.zipapp import ensure_file_on_disk
21 from virtualenv.version import __version__
22
23 HERE = Path(os.path.abspath(__file__)).parent
24 DEBUG_SCRIPT = HERE / "debug.py"
25
26
27 class CreatorMeta(object):
28 def __init__(self):
29 self.error = None
30
31
32 @add_metaclass(ABCMeta)
33 class Creator(object):
34 """A class that given a python Interpreter creates a virtual environment"""
35
36 def __init__(self, options, interpreter):
37 """Construct a new virtual environment creator.
38
39 :param options: the CLI option as parsed from :meth:`add_parser_arguments`
40 :param interpreter: the interpreter to create virtual environment from
41 """
42 self.interpreter = interpreter
43 self._debug = None
44 self.dest = Path(options.dest)
45 self.clear = options.clear
46 self.pyenv_cfg = PyEnvCfg.from_folder(self.dest)
47 self.app_data = options.app_data.folder
48
49 def __repr__(self):
50 return ensure_str(self.__unicode__())
51
52 def __unicode__(self):
53 return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args()))
54
55 def _args(self):
56 return [
57 ("dest", ensure_text(str(self.dest))),
58 ("clear", self.clear),
59 ]
60
61 @classmethod
62 def can_create(cls, interpreter):
63 """Determine if we can create a virtual environment.
64
65 :param interpreter: the interpreter in question
66 :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \
67 :meth:`add_parser_arguments`
68 """
69 return True
70
71 @classmethod
72 def add_parser_arguments(cls, parser, interpreter, meta, app_data):
73 """Add CLI arguments for the creator.
74
75 :param parser: the CLI parser
76 :param interpreter: the interpreter we're asked to create virtual environment for
77 :param meta: value as returned by :meth:`can_create`
78 """
79 parser.add_argument(
80 "dest", help="directory to create virtualenv at", type=cls.validate_dest,
81 )
82 parser.add_argument(
83 "--clear",
84 dest="clear",
85 action="store_true",
86 help="remove the destination directory if exist before starting (will overwrite files otherwise)",
87 default=False,
88 )
89
90 @abstractmethod
91 def create(self):
92 """Perform the virtual environment creation."""
93 raise NotImplementedError
94
95 @classmethod
96 def validate_dest(cls, raw_value):
97 """No path separator in the path, valid chars and must be write-able"""
98
99 def non_write_able(dest, value):
100 common = Path(*os.path.commonprefix([value.parts, dest.parts]))
101 raise ArgumentTypeError(
102 "the destination {} is not write-able at {}".format(dest.relative_to(common), common)
103 )
104
105 # the file system must be able to encode
106 # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
107 encoding = sys.getfilesystemencoding()
108 refused = OrderedDict()
109 kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}
110 for char in ensure_text(raw_value):
111 try:
112 trip = char.encode(encoding, **kwargs).decode(encoding)
113 if trip == char:
114 continue
115 raise ValueError(trip)
116 except ValueError:
117 refused[char] = None
118 if refused:
119 raise ArgumentTypeError(
120 "the file system codec ({}) cannot handle characters {!r} within {!r}".format(
121 encoding, "".join(refused.keys()), raw_value
122 )
123 )
124 if os.pathsep in raw_value:
125 raise ArgumentTypeError(
126 "destination {!r} must not contain the path separator ({}) as this would break "
127 "the activation scripts".format(raw_value, os.pathsep)
128 )
129
130 value = Path(raw_value)
131 if value.exists() and value.is_file():
132 raise ArgumentTypeError("the destination {} already exists and is a file".format(value))
133 if (3, 3) <= sys.version_info <= (3, 6):
134 # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation
135 dest = Path(os.path.realpath(raw_value))
136 else:
137 dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both
138 value = dest
139 while dest:
140 if dest.exists():
141 if os.access(ensure_text(str(dest)), os.W_OK):
142 break
143 else:
144 non_write_able(dest, value)
145 base, _ = dest.parent, dest.name
146 if base == dest:
147 non_write_able(dest, value) # pragma: no cover
148 dest = base
149 return str(value)
150
151 def run(self):
152 if self.dest.exists() and self.clear:
153 logging.debug("delete %s", self.dest)
154 safe_delete(self.dest)
155 self.create()
156 self.set_pyenv_cfg()
157
158 def set_pyenv_cfg(self):
159 self.pyenv_cfg.content = OrderedDict()
160 self.pyenv_cfg["home"] = self.interpreter.system_exec_prefix
161 self.pyenv_cfg["implementation"] = self.interpreter.implementation
162 self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)
163 self.pyenv_cfg["virtualenv"] = __version__
164
165 @property
166 def debug(self):
167 """
168 :return: debug information about the virtual environment (only valid after :meth:`create` has run)
169 """
170 if self._debug is None and self.exe is not None:
171 self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data)
172 return self._debug
173
174 # noinspection PyMethodMayBeStatic
175 def debug_script(self):
176 return DEBUG_SCRIPT
177
178
179 def get_env_debug_info(env_exe, debug_script, app_data):
180 env = os.environ.copy()
181 env.pop(str("PYTHONPATH"), None)
182
183 with ensure_file_on_disk(debug_script, app_data) as debug_script:
184 cmd = [str(env_exe), str(debug_script)]
185 if WIN_CPYTHON_2:
186 cmd = [ensure_text(i) for i in cmd]
187 logging.debug(str("debug via %r"), LogCmd(cmd))
188 code, out, err = run_cmd(cmd)
189
190 # noinspection PyBroadException
191 try:
192 if code != 0:
193 result = literal_eval(out)
194 else:
195 result = json.loads(out)
196 if err:
197 result["err"] = err
198 except Exception as exception:
199 return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}
200 if "sys" in result and "path" in result["sys"]:
201 del result["sys"]["path"][0]
202 return result