Skip to content

The venv-retarget Python API

Commonly-used functions

venv_retarget.detect_path(cfg, venvdir, *, use_prefix=False)

Examine a virtual environment, determine the path it thinks it is at.

Source code in src/venv_retarget/detect.py
def detect_path(cfg: defs.Config, venvdir: pathlib.Path, *, use_prefix: bool = False) -> Detected:
    """Examine a virtual environment, determine the path it thinks it is at."""
    cfg.log.debug("Examining %(venvdir)s", {"venvdir": venvdir})

    cache_path: Final = _detect_path_cache(cfg, venvdir)
    cfg.log.debug("- got cache_path %(cache_path)r", {"cache_path": cache_path})

    cfg_path: Final = _detect_path_pyvenv(cfg, venvdir)
    cfg.log.debug("- got cfg_path %(cfg_path)r", {"cfg_path": cfg_path})

    prefix_path: Final = _detect_path_prefix(cfg, venvdir) if use_prefix else None
    cfg.log.debug("- got prefix_path %(prefix_path)r", {"prefix_path": prefix_path})

    paths: Final = sorted(
        {path for path in (cfg_path, prefix_path, cache_path) if path is not None},
    )
    match paths:
        case []:
            raise NoPathsError(venvdir)

        case [single]:
            return Detected(
                path=single,
                cfg_path=cfg_path,
                cache_path=cache_path,
                prefix_path=prefix_path,
            )

        case too_many_paths:
            raise ConflictingPathsError(venvdir, paths=too_many_paths)

venv_retarget.retarget(cfg, venvdir, *, venvsrc=None, venvdst=None)

Modify the files within the virtual environment to prepare it for moving.

Source code in src/venv_retarget/impl.py
def retarget(
    cfg: defs.Config,
    venvdir: pathlib.Path,
    *,
    venvsrc: pathlib.Path | None = None,
    venvdst: pathlib.Path | None = None,
) -> None:
    """Modify the files within the virtual environment to prepare it for moving."""
    python, venvsrc, venvdst = _detect_and_validate(cfg, venvdir, venvsrc=venvsrc, venvdst=venvdst)
    recompile, replace, ignore = _examine_files(venvdir, venvsrc, python)
    cfg.log.info(
        (
            "- %(recompile)d directories to recompile, "
            "%(replace)d files to replace strings in, "
            "%(ignore)d files ignored"
        ),
        {"recompile": len(recompile), "replace": len(replace), "ignore": len(ignore)},
    )

    for path in recompile:
        _recompile_cached(cfg, path, venvdir, python, venvsrc=venvsrc, venvdst=venvdst)

    for path in replace:
        _replace_strings(cfg, path, venvsrc, venvdst)

Commonly-used data structures

venv_retarget.defs.Config dataclass

Runtime configuration for the venv-retarget library.

Source code in src/venv_retarget/defs.py
@dataclasses.dataclass(frozen=True)
class Config:
    """Runtime configuration for the venv-retarget library."""

    log: logging.Logger
    """The logger to send diagnostic, informational, and error messages to."""

    verbose: bool
    """Verbose operation; display diagnostic output."""

log: logging.Logger instance-attribute

The logger to send diagnostic, informational, and error messages to.

verbose: bool instance-attribute

Verbose operation; display diagnostic output.

Errors

venv_retarget.defs.Error dataclass

Bases: Exception

An error that occurred while examining or processing a virtual environment.

Source code in src/venv_retarget/defs.py
@dataclasses.dataclass
class Error(Exception):
    """An error that occurred while examining or processing a virtual environment."""

    venvdir: pathlib.Path
    """The virtual environment that we tried to examine or process."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return f"Could not process the {self.venvdir} virtual environment"

venvdir: pathlib.Path instance-attribute

The virtual environment that we tried to examine or process.

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/defs.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return f"Could not process the {self.venvdir} virtual environment"

venv_retarget.detect.DetectError dataclass

Bases: Error

An error that occurred while examining a virtual environment.

Source code in src/venv_retarget/detect.py
@dataclasses.dataclass
class DetectError(defs.Error):
    """An error that occurred while examining a virtual environment."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return f"Could not examine the {self.venvdir} virtual environment"

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/detect.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return f"Could not examine the {self.venvdir} virtual environment"

venv_retarget.detect.NoPathsError dataclass

Bases: DetectError

The virtual environment does not seem to know where it is at.

Source code in src/venv_retarget/detect.py
@dataclasses.dataclass
class NoPathsError(DetectError):
    """The virtual environment does not seem to know where it is at."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return f"Could not find a venv path in any of the {self.venvdir} configuration files"

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/detect.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return f"Could not find a venv path in any of the {self.venvdir} configuration files"

venv_retarget.detect.ConflictingPathsError dataclass

Bases: DetectError

The virtual environment seems to be confused about where it is at.

Source code in src/venv_retarget/detect.py
@dataclasses.dataclass
class ConflictingPathsError(DetectError):
    """The virtual environment seems to be confused about where it is at."""

    paths: list[pathlib.Path]
    """The paths that the virtual environment thinks it is at, at the same time."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return (
            f"Conflicting paths found in the {self.venvdir} configuration files: "
            f"{' '.join(str(path) for path in self.paths)}"
        )

paths: list[pathlib.Path] instance-attribute

The paths that the virtual environment thinks it is at, at the same time.

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/detect.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return (
        f"Conflicting paths found in the {self.venvdir} configuration files: "
        f"{' '.join(str(path) for path in self.paths)}"
    )

venv_retarget.impl.ProcessError dataclass

Bases: Error

An error that occurred while processing the virtual environment.

Source code in src/venv_retarget/impl.py
@dataclasses.dataclass
class ProcessError(defs.Error):
    """An error that occurred while processing the virtual environment."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return f"Could not process the {self.venvdir} virtual environment"

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/impl.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return f"Could not process the {self.venvdir} virtual environment"

venv_retarget.impl.SameSrcDstError dataclass

Bases: ProcessError

Neither a from- nor a to-path was specified.

Source code in src/venv_retarget/impl.py
@dataclasses.dataclass
class SameSrcDstError(ProcessError):
    """Neither a from- nor a to-path was specified."""

    src: pathlib.Path
    """The source that appears to be the same as the destination."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return (
            f"Retargeting {self.venvdir}: "
            f"the same {self.src} specified as both source and destination"
        )

src: pathlib.Path instance-attribute

The source that appears to be the same as the destination.

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/impl.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return (
        f"Retargeting {self.venvdir}: "
        f"the same {self.src} specified as both source and destination"
    )

venv_retarget.impl.NotAbsolutePathError dataclass

Bases: ProcessError

The source and destination paths must be absolute.

Source code in src/venv_retarget/impl.py
@dataclasses.dataclass
class NotAbsolutePathError(ProcessError):
    """The source and destination paths must be absolute."""

    path: pathlib.Path
    """The non-absolute path that was specified."""

    def __str__(self) -> str:
        """Provide a human-readable description of the error."""
        return f"Retargeting {self.venvdir}: non-absolute path {self.path} specified"

path: pathlib.Path instance-attribute

The non-absolute path that was specified.

__str__()

Provide a human-readable description of the error.

Source code in src/venv_retarget/impl.py
def __str__(self) -> str:
    """Provide a human-readable description of the error."""
    return f"Retargeting {self.venvdir}: non-absolute path {self.path} specified"

Miscellaneous

venv_retarget.defs.VERSION: Final = '0.1.0' module-attribute

The venv-retarget library version, semver-like.

venv_retarget.detect.detect_python_properties(cfg, venvdir) cached

Query the virtual environment's Python interpreter for its settings.

Source code in src/venv_retarget/detect.py
@functools.lru_cache
def detect_python_properties(cfg: defs.Config, venvdir: pathlib.Path) -> Python:
    """Query the virtual environment's Python interpreter for its settings."""
    exe_ext: Final = sysconfig.get_config_var("EXE")
    python: Final = venvdir / "bin" / f"python3{exe_ext}"
    cfg.log.debug("- querying %(python)s for its properties", {"python": python})
    raw: Final = subprocess.check_output(  # noqa: S603
        [
            python,
            "-c",
            (
                "import json; "
                "import sys; "
                "print(json.dumps({"
                '    "cache_tag": sys.implementation.cache_tag,'
                '    "libs": [path for path in sys.path if path.startswith(sys.prefix)],'
                '    "prefix": sys.prefix,'
                '    "version_info": sys.version_info,'
                "}))"
            ),
        ],
        encoding="UTF-8",
    )
    decoded: Final = json.loads(raw)
    cfg.log.debug("- got %(prop)r", {"python": python, "prop": decoded})
    return Python(
        cache_tag=decoded["cache_tag"],
        interpreter=python,
        libs=[pathlib.Path(lib) for lib in decoded["libs"]],
        prefix=decoded["prefix"],
        version_info=(
            decoded["version_info"][0],
            decoded["version_info"][1],
            decoded["version_info"][3],
        ),
    )