Source code for runway.dependency_managers._pipenv

"""Pipenv interface."""
from __future__ import annotations

import locale
import logging
import re
import subprocess
from pathlib import Path
from typing import TYPE_CHECKING, Any, Tuple

from typing_extensions import Final, Literal

from ..compat import cached_property
from ..exceptions import RunwayError
from ..utils import Version
from .base_classes import DependencyManager

if TYPE_CHECKING:
    from _typeshed import StrPath

LOGGER = logging.getLogger(__name__)


[docs]class PipenvExportFailedError(RunwayError): """Pipenv export failed to produce a ``requirements.txt`` file."""
[docs] def __init__(self, *args: Any, **kwargs: Any) -> None: """Instantiate class. All args/kwargs are passed to parent method.""" self.message = ( "pipenv lock to requirements.txt format failed; review pipenv's" " output above to troubleshoot" ) super().__init__(*args, **kwargs)
[docs]class PipenvNotFoundError(RunwayError): """Pipenv not installed or found in $PATH."""
[docs] def __init__(self, *args: Any, **kwargs: Any) -> None: """Instantiate class. All args/kwargs are passed to parent method.""" self.message = ( "pipenv not installed or not in PATH! " "Install it according to pipenv docs (https://pipenv.pypa.io/en/latest/) " "and ensure it is available in PATH." ) super().__init__(*args, **kwargs)
[docs]class Pipenv(DependencyManager): """Pipenv dependency manager.""" CONFIG_FILES: Final[Tuple[Literal["Pipfile"], Literal["Pipfile.lock"]]] = ( "Pipfile", "Pipfile.lock", ) """Configuration files used by pipenv.""" EXECUTABLE: Final[Literal["pipenv"]] = "pipenv" """CLI executable.""" @cached_property def version(self) -> Version: """pipenv version.""" cmd_output = self._run_command([self.EXECUTABLE, "--version"]) match = re.search(r"^pipenv, version (?P<version>\S*)", cmd_output) if not match: LOGGER.warning( "unable to parse pipenv version from output:\n%s", cmd_output ) return Version("0.0.0") return Version(match.group("version"))
[docs] @classmethod def dir_is_project(cls, directory: StrPath, **__kwargs: Any) -> bool: """Determine if the directory contains a project for this dependency manager. Args: directory: Directory to check. """ dir_path = Path(directory) if not (dir_path / Pipenv.CONFIG_FILES[0]).is_file(): return False if not (dir_path / Pipenv.CONFIG_FILES[1]).is_file(): LOGGER.warning("%s not found", Pipenv.CONFIG_FILES[1]) return True
[docs] def export(self, *, dev: bool = False, output: StrPath) -> Path: """Export the lock file to other formats (requirements.txt only). The underlying command being executed by this method is ``pipenv lock --requirements``. Args: dev: Include development dependencies. output: Path to the output file. """ output = Path(output) try: result = self._run_command( self.generate_command( "lock", dev=dev, requirements=True, ), suppress_output=True, ) except subprocess.CalledProcessError as exc: raise PipenvExportFailedError from exc output.parent.mkdir(exist_ok=True, parents=True) # ensure directory exists # python3.7 w/ pylint 2.12.[12] crashes if result is not wrapped in str() output.write_text( str(result), encoding=locale.getpreferredencoding(do_setlocale=False) ) return output