Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
creator.py227 linesDownload Raw Back to create
1from __future__ import annotations2 3import json4import logging5import os6import sys7from abc import ABC, abstractmethod8from argparse import ArgumentTypeError9from ast import literal_eval10from collections import OrderedDict11from pathlib import Path12 13from virtualenv.discovery.cached_py_info import LogCmd14from virtualenv.util.path import safe_delete15from virtualenv.util.subprocess import run_cmd16from virtualenv.version import __version__17 18from .pyenv_cfg import PyEnvCfg19 20HERE = Path(os.path.abspath(__file__)).parent21DEBUG_SCRIPT = HERE / "debug.py"22 23 24class CreatorMeta:25    def __init__(self) -> None:26        self.error = None27 28 29class Creator(ABC):30    """A class that given a python Interpreter creates a virtual environment."""31 32    def __init__(self, options, interpreter) -> None:33        """34        Construct a new virtual environment creator.35 36        :param options: the CLI option as parsed from :meth:`add_parser_arguments`37        :param interpreter: the interpreter to create virtual environment from38        """39        self.interpreter = interpreter40        self._debug = None41        self.dest = Path(options.dest)42        self.clear = options.clear43        self.no_vcs_ignore = options.no_vcs_ignore44        self.pyenv_cfg = PyEnvCfg.from_folder(self.dest)45        self.app_data = options.app_data46        self.env = options.env47 48    def __repr__(self) -> str:49        return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})"50 51    def _args(self):52        return [53            ("dest", str(self.dest)),54            ("clear", self.clear),55            ("no_vcs_ignore", self.no_vcs_ignore),56        ]57 58    @classmethod59    def can_create(cls, interpreter):  # noqa: ARG00360        """61        Determine if we can create a virtual environment.62 63        :param interpreter: the interpreter in question64        :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \65                  :meth:`add_parser_arguments`66        """67        return True68 69    @classmethod70    def add_parser_arguments(cls, parser, interpreter, meta, app_data):  # noqa: ARG00371        """72        Add CLI arguments for the creator.73 74        :param parser: the CLI parser75        :param app_data: the application data folder76        :param interpreter: the interpreter we're asked to create virtual environment for77        :param meta: value as returned by :meth:`can_create`78        """79        parser.add_argument(80            "dest",81            help="directory to create virtualenv at",82            type=cls.validate_dest,83        )84        parser.add_argument(85            "--clear",86            dest="clear",87            action="store_true",88            help="remove the destination directory if exist before starting (will overwrite files otherwise)",89            default=False,90        )91        parser.add_argument(92            "--no-vcs-ignore",93            dest="no_vcs_ignore",94            action="store_true",95            help="don't create VCS ignore directive in the destination directory",96            default=False,97        )98 99    @abstractmethod100    def create(self):101        """Perform the virtual environment creation."""102        raise NotImplementedError103 104    @classmethod105    def validate_dest(cls, raw_value):  # noqa: C901106        """No path separator in the path, valid chars and must be write-able."""107 108        def non_write_able(dest, value):109            common = Path(*os.path.commonprefix([value.parts, dest.parts]))110            msg = f"the destination {dest.relative_to(common)} is not write-able at {common}"111            raise ArgumentTypeError(msg)112 113        # the file system must be able to encode114        # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/115        encoding = sys.getfilesystemencoding()116        refused = OrderedDict()117        kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}118        for char in str(raw_value):119            try:120                trip = char.encode(encoding, **kwargs).decode(encoding)121                if trip == char:122                    continue123                raise ValueError(trip)  # noqa: TRY301124            except ValueError:125                refused[char] = None126        if refused:127            bad = "".join(refused.keys())128            msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}"129            raise ArgumentTypeError(msg)130        if os.pathsep in raw_value:131            msg = (132                f"destination {raw_value!r} must not contain the path separator ({os.pathsep})"133                f" as this would break the activation scripts"134            )135            raise ArgumentTypeError(msg)136 137        value = Path(raw_value)138        if value.exists() and value.is_file():139            msg = f"the destination {value} already exists and is a file"140            raise ArgumentTypeError(msg)141        dest = Path(os.path.abspath(str(value))).resolve()  # on Windows absolute does not imply resolve so use both142        value = dest143        while dest:144            if dest.exists():145                if os.access(str(dest), os.W_OK):146                    break147                non_write_able(dest, value)148            base, _ = dest.parent, dest.name149            if base == dest:150                non_write_able(dest, value)  # pragma: no cover151            dest = base152        return str(value)153 154    def run(self):155        if self.dest.exists() and self.clear:156            logging.debug("delete %s", self.dest)157            safe_delete(self.dest)158        self.create()159        self.set_pyenv_cfg()160        if not self.no_vcs_ignore:161            self.setup_ignore_vcs()162 163    def set_pyenv_cfg(self):164        self.pyenv_cfg.content = OrderedDict()165        self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(self.interpreter.system_executable))166        self.pyenv_cfg["implementation"] = self.interpreter.implementation167        self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)168        self.pyenv_cfg["virtualenv"] = __version__169 170    def setup_ignore_vcs(self):171        """Generate ignore instructions for version control systems."""172        # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs173        git_ignore = self.dest / ".gitignore"174        if not git_ignore.exists():175            git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")176        # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the177        # subinclude directive from root, at which point on might as well ignore the directory itself, see178        # https://www.selenic.com/mercurial/hgignore.5.html for more details179        # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore180        # Subversion - does not support ignore files, requires direct manipulation with the svn tool181 182    @property183    def debug(self):184        """:return: debug information about the virtual environment (only valid after :meth:`create` has run)"""185        if self._debug is None and self.exe is not None:186            self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env)187        return self._debug188 189    @staticmethod190    def debug_script():191        return DEBUG_SCRIPT192 193 194def get_env_debug_info(env_exe, debug_script, app_data, env):195    env = env.copy()196    env.pop("PYTHONPATH", None)197 198    with app_data.ensure_extracted(debug_script) as debug_script_extracted:199        cmd = [str(env_exe), str(debug_script_extracted)]200        logging.debug("debug via %r", LogCmd(cmd))201        code, out, err = run_cmd(cmd)202 203    try:204        if code != 0:205            if out:206                result = literal_eval(out)207            else:208                if code == 2 and "file" in err:  # noqa: PLR2004209                    # Re-raise FileNotFoundError from `run_cmd()`210                    raise OSError(err)  # noqa: TRY301211                raise Exception(err)  # noqa: TRY002, TRY301212        else:213            result = json.loads(out)214        if err:215            result["err"] = err216    except Exception as exception:  # noqa: BLE001217        return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}218    if "sys" in result and "path" in result["sys"]:219        del result["sys"]["path"][0]220    return result221 222 223__all__ = [224    "Creator",225    "CreatorMeta",226]227 
codekingpro/portable-devtools · Team Ai