PK!3((pipis/__init__.py""" 'pipis' stands for 'pip isolated' """ from .cli import cli, show_version, list_installed, freeze, install, update, uninstall __all__ = [ "cli", "show_version", "list_installed", "freeze", "install", "update", "uninstall", ] if __name__ == "__main__": cli() PK!1Lee pipis/cli.py""" commands for pipis """ import os import click from tabulate import tabulate from . import lib as p CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) def cli(): """ Pipis is a wrapper around venv and pip which installs python packages into separate venvs to shield them from your system and each other. It creates a venv in `~/.local/venvs/`, updates pip, installs the package, and links the package's scripts to `~/.local/bin/`. These directory can be changed respectively through the environment variables `PIPIS_VENVS` and `PIPIS_BIN`. """ pass @cli.command("version", context_settings=CONTEXT_SETTINGS) def show_version(): """Show version and exit.""" package_version = p.get_version("pipis") click.echo(package_version) @cli.command("list", context_settings=CONTEXT_SETTINGS) def list_installed(): """List installed packages.""" pipis_venvs, _ = p.get_pipis() table = {"Package": [], "Version": []} for package in sorted(os.listdir(pipis_venvs)): package_version = p.get_version(package) table["Package"].append(package) table["Version"].append(package_version) click.echo(tabulate(table, headers="keys")) @cli.command(context_settings=CONTEXT_SETTINGS) def freeze(): """Output installed packages in requirements format.""" pipis_venvs, _ = p.get_pipis() for package in sorted(os.listdir(pipis_venvs)): package_version = p.get_version(package) click.echo("{}=={}".format(package, package_version)) @cli.command(context_settings=CONTEXT_SETTINGS, short_help="Install packages.") @click.option( "-y", "--yes", is_flag=True, callback=p.abort_if_false, prompt="Do you want to continue?", expose_value=False, help="Confirm the action without prompting.", ) @click.option("-r", "--requirement", help="Install from the given requirements file.") @click.option( "-d", "--dependency", help="Add the specified package as dependency in the venv." ) @click.option( "-s", "--system", is_flag=True, help="Give the virtual environment access to the system site-packages dir.", ) @click.option( "-U", "--upgrade", is_flag=True, help="Upgrade all specified packages to the newest available version.", ) @click.option( "-I", "--ignore-installed", is_flag=True, help="Ignore the installed packages (reinstalling instead).", ) @click.option("-v", "--verbose", is_flag=True, help="Give more output.") @click.argument("name", nargs=-1, type=click.STRING) def install(**kwargs): """ Install packages, where NAME is the package name. You can specify multiple names. Packages names and "requirements files" are mutually exclusive. """ # check presence of args if not (kwargs["requirement"] or kwargs["name"]): raise click.UsageError("missing arguments/options") # check mutually esclusive args if kwargs["requirement"] and kwargs["name"]: raise click.UsageError("too many arguments/options") # populate packages list with req file if kwargs["requirement"]: kwargs["name"] = p.get_requirement(kwargs["requirement"]) # do not add dependency to multiple packages if len(kwargs["name"]) > 1 and kwargs["dependency"]: raise click.UsageError("cannot add dependecy to multiple packages") # process packages with click.progressbar( kwargs["name"], label="Installing", item_show_func=p.show_package ) as packages: for package in packages: p.venv(package, kwargs["system"]) cmd = p.install( package, kwargs["verbose"], kwargs["upgrade"], kwargs["ignore_installed"], ) p.install_dep(cmd, package, kwargs["dependency"]) p.link(package) @cli.command(context_settings=CONTEXT_SETTINGS, short_help="Update packages.") @click.option( "-y", "--yes", is_flag=True, callback=p.abort_if_false, prompt="Do you want to continue?", expose_value=False, help="Confirm the action without prompting.", ) @click.option("-r", "--requirement", help="Install from the given requirements file.") @click.option( "-I", "--ignore-installed", is_flag=True, help="Ignore the installed packages (reinstalling instead).", ) @click.option("-v", "--verbose", is_flag=True, help="Give more output.") @click.argument("name", nargs=-1, type=click.STRING) def update(**kwargs): """ Update packages, where NAME is the package name. You can specify multiple names. Packages names and "requirements files" are mutually exclusive. If you do not specify package name or requirements file, it will update all installed packages. """ pipis_venvs, _ = p.get_pipis() # check mutually esclusive args if kwargs["requirement"] and kwargs["name"]: raise click.UsageError("too many arguments/options") # populate packages list with req file if kwargs["requirement"]: kwargs["name"] = p.get_requirement(kwargs["requirement"]) # populate packages list with all currently installed if not kwargs["name"]: kwargs["name"] = os.listdir(pipis_venvs) with click.progressbar( kwargs["name"], label="Updating", item_show_func=p.show_package ) as packages: for package in packages: p.venv(package, upgrade=True) cmd = p.install( package, kwargs["verbose"], upgrade=True, ignore=kwargs["ignore_installed"], ) p.install_dep(cmd, package) p.link(package, upgrade=True) @cli.command(context_settings=CONTEXT_SETTINGS, short_help="Uninstall packages.") @click.option( "-y", "--yes", is_flag=True, callback=p.abort_if_false, prompt="Do you want to continue?", expose_value=False, help="Confirm the action without prompting.", ) @click.option("-r", "--requirement", help="Install from the given requirements file.") @click.argument("name", nargs=-1, type=click.STRING) def uninstall(**kwargs): """ Uninstall packages, where NAME is the package name. You can specify multiple names. Packages names and "requirements files" are mutually exclusive. """ # check presence of args if not (kwargs["requirement"] or kwargs["name"]): raise click.UsageError("missing arguments/options") # check mutually esclusive args if kwargs["requirement"] and kwargs["name"]: raise click.UsageError("too many arguments/options") # populate packages list with req file if kwargs["requirement"]: kwargs["name"] = p.get_requirement(kwargs["requirement"]) with click.progressbar( kwargs["name"], label="Removing", item_show_func=p.show_package ) as packages: for package in packages: p.remove(package) PK!LL pipis/lib.py""" helpers lib for pipis """ import importlib from operator import methodcaller import os from shutil import rmtree from subprocess import check_call, check_output, CalledProcessError import sys from time import time from venv import EnvBuilder import click import pkg_resources DEFAULT_PIPIS_VENVS = "~/.local/venvs/" DEFAULT_PIPIS_BIN = "~/.local/bin/" def get_pipis(): """Get pipis environment variables or fallback on defaults.""" pipis_venvs = os.path.expanduser(os.environ.get("PIPIS_VENVS", DEFAULT_PIPIS_VENVS)) pipis_bin = os.path.expanduser(os.environ.get("PIPIS_BIN", DEFAULT_PIPIS_BIN)) return pipis_venvs, pipis_bin def get_package(package): """Normalize package name and version.""" req = pkg_resources.Requirement(package) package_name = req.project_name package_spec = str(req.specifier) return package_name, package_spec def get_venv(package): """Get vevn path.""" pipis_venvs, _ = get_pipis() venv_dir = os.path.join(pipis_venvs, package) venv_py = os.path.join(venv_dir, "bin", "python") return venv_dir, venv_py def get_dist(package): """Get dist object for a given package.""" venv_dir, venv_py = get_venv(package) # get the module path from its venv and append it to the current path cmd_path = [ "import sys;", "p = list(filter(lambda x: x.startswith('{}'), sys.path));".format(venv_dir), "print(p[0]);", ] cmd_path = " ".join(cmd_path) venv_path = check_output([venv_py, "-c", cmd_path]) venv_path = venv_path.decode("utf-8").strip() sys.path.append(venv_path) # reload pkg_resources module to take into account new path importlib.reload(pkg_resources) # get informations about package dist = pkg_resources.get_distribution(package) # remove package venv from current path index = sys.path.index(venv_path) del sys.path[index] return dist def get_version(package): """Get version number for a given package.""" dist = get_dist(package) return dist.version def get_scripts(package): """Get executables scripts for a given package.""" venv_dir, _ = get_venv(package) # get informations about package dist = get_dist(package) # init list entry_points = [] # get entry_points from RECORD file if dist.has_metadata("RECORD"): files = dist.get_metadata_lines("RECORD") for line in files: line = os.path.join(dist.location, line.split(",")[0]) line = os.path.normpath(line) entry_points.append(line) # get entry_points from installed-files.txt file elif dist.has_metadata("installed-files.txt"): files = dist.get_metadata_lines("installed-files.txt") for line in files: line = os.path.join(dist.egg_info, line.split(",")[0]) line = os.path.normpath(line) entry_points.append(line) # get entry_points from entry_points.txt file elif dist.has_metadata("entry_points.txt"): entry_points = dist.get_entry_map("console_scripts").keys() # filter only binaries entry_points = list( filter(methodcaller("startswith", os.path.join(venv_dir, "bin")), entry_points) ) return entry_points def abort_if_false(ctx, _, value): """Abort callback function.""" if not value: ctx.abort() def show_package(package): """Show package callback function.""" return package def get_requirement(requirement): """Get packages list of a given requirements file.""" try: with open(requirement, "r") as req: return req.read().splitlines() except IOError: raise click.FileError(requirement) def set_requirement(package, dependency): """Create or append requirements files for a given package.""" venv_dir, _ = get_venv(package) requirement = os.path.join(venv_dir, "requirements.txt") req_content = set() if os.path.exists(requirement): with open(requirement, "r") as req: req_content = set(req.read().splitlines()) if dependency not in req_content: req_content.add(dependency) with open(requirement, "w") as req: req.write("\n".join(sorted(req_content))) req.write("\n") return requirement def venv(package, system=False, upgrade=False): """Define venv options and create it.""" package, _ = get_package(package) venv_dir, _ = get_venv(package) # create or update venv venv_build = EnvBuilder( system_site_packages=system, clear=False, symlinks=True, upgrade=upgrade, with_pip=True, ) if not os.path.isdir(venv_dir) or upgrade: venv_build.create(venv_dir) def install(package, verbose=False, upgrade=False, ignore=False): """Install a given package into its venv.""" package, version = get_package(package) venv_dir, venv_py = get_venv(package) # define pip install cmd cmd = [venv_py, "-m", "pip", "install"] # set verbosity if not verbose: cmd.append("--quiet") # upgrade pip in venv check_call(cmd + ["--upgrade", "pip"]) # set upgrade if upgrade: cmd.append("--upgrade") # set reinstall if ignore: cmd.append("--ignore-installed") # install package (and eventual dependencies) in venv try: check_call(cmd + [package + version]) except CalledProcessError: rmtree(venv_dir) message = "Cannot install {}".format(package) raise click.BadArgumentUsage(message) return cmd def install_dep(cmd, package, dependency=None): """Install a given dependency package into the package venv.""" package, _ = get_package(package) venv_dir, _ = get_venv(package) # set requirements file path dependencies = os.path.join(venv_dir, "requirements.txt") # if a dependency is passed, add it to requirements if dependency: dependencies = set_requirement(package, dependency) # install dependencies if needed if os.path.exists(dependencies): check_call(cmd + ["--requirement", dependencies]) def link(package, upgrade=False): """Create or update symlinks for a given package.""" _, pipis_bin = get_pipis() package, _ = get_package(package) venv_dir, _ = get_venv(package) scripts = get_scripts(package) # check if there is no scripts if len(scripts) < 1: rmtree(venv_dir) message = "library installation is not supported by pipis" raise click.ClickException(message) # link each script found for script in scripts: script_name = script.split("/")[-1] target = os.path.join(pipis_bin, script_name) if os.path.realpath(target) == script: # already exists continue elif upgrade and not os.path.islink(target): # create os.symlink(script, target) else: # replace existing target temp_link = target + str(time()) os.symlink(script, temp_link) os.replace(temp_link, target) def remove(package): """Remove package venv and scripts.""" pipis_venvs, pipis_bin = get_pipis() package, _ = get_package(package) venv_dir = os.path.join(pipis_venvs, package) if os.path.isdir(venv_dir): # remove scripts symlink scripts = get_scripts(package) for script in scripts: script_name = script.split("/")[-1] target = os.path.join(pipis_bin, script_name) if os.path.islink(target): os.remove(target) # remove package venv rmtree(venv_dir) else: click.secho(" is not installed, skip", fg="yellow") PK!H"!#&pipis-1.5.0.dist-info/entry_points.txtN+I/N.,()*,,V9\\PK!3qpipis-1.5.0.dist-info/LICENSE DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. PK!HƣxSTpipis-1.5.0.dist-info/WHEEL A н#J;/"d&F]xzw>@Zpy3F ]n2H%_60{8&baPa>PK!H:Kipipis-1.5.0.dist-info/METADATAXr8۩!98U$7X8lj%"vix:}>X*4gw|-˙e{PrD_%OcVբ&x0(i \<&\fviR9tƳBR-ڻRU7u!p':[ f%1hmZ.Ja-Xrуd:{8E ͪ1\枞>$|ɳs<0xqj] O_,$*M}T]5Ӝ]ryX7NG"[oN^ _H7)P rG|3|i 2FQ ;m|֨v?eG4O(L[ny>Qx{ƒY:`N{1Y-J^ Yh^+m)^̯9-tL{nNԾojB27Pi"]7DEZkg6yL^wJBܪ:7F*F4v͹x4"^юgq(sCh>" wpNhtюsߡu!pF$)L(?r)/pN:5 +5gy[r;3۠$lZ ԥJj Tx kӹ\e&}KrJK1L [׊0mٜAX\ tdMƇY7Ζ\}E` a7Bwlh_N[ 7- if v=I3n )"X!V5LhNgФ汥 @@HH3MJһǤ!5ufeIgE[-16rB 'g-eL")+0K`4ZuRmKv…}~:xdz>>-IeCm :5 , iMga&Aal9@Tcy%c 33dEVr&E+vg9ԜQ5]v(fQ@*F0 kصnaxE2㶎_WK⚮:*%-皝! ;3=&/o~ rMZe);_l|r R rérn Sd|lJҤߣI0"IMӠ{x~Vsj3=pW!dޤKʮsXxV>xvnv@ckB+b5FQY]4ڦxt ɮAtيRlEdűfs4EzdI!LFduKu$sRɥG}`KP)h l'W-݁@U!.ܠYrM];S'hhhF,# 5R|r]2*]QǹÞOD@X;6iC43ZI `i8ha_iS/PK!3((pipis/__init__.pyPK!1Lee Wpipis/cli.pyPK!LL pipis/lib.pyPK!H"!#&\;pipis-1.5.0.dist-info/entry_points.txtPK!3q;pipis-1.5.0.dist-info/LICENSEPK!HƣxST=pipis-1.5.0.dist-info/WHEELPK!H:Kig>pipis-1.5.0.dist-info/METADATAPK!H-MEpipis-1.5.0.dist-info/RECORDPK1CG