PK!\aU'U'pipis/__init__.py""" 'pipis' stands for 'pip isolated' """ import importlib from operator import methodcaller import os from shutil import rmtree from subprocess import check_call, check_output import sys from venv import create import click import pkg_resources VENV_ROOT_DIR = os.path.expanduser('~/.local/venvs/') BIN_DIR = os.path.expanduser('~/.local/bin/') def _get_venv_data(package): venv_dir = os.path.join(VENV_ROOT_DIR, package) venv_py = os.path.join(venv_dir, 'bin', 'python') return venv_dir, venv_py def _get_dist(package): _, venv_py = _get_venv_data(package) # get the module path from its venv and append it to the current path cmd_last_path = 'import sys; print(sys.path[-1])' venv_path = check_output([venv_py, '-c', cmd_last_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 del sys.path[-1] return dist def _get_version(package): dist = _get_dist(package) return dist.version def _get_console_scripts(package): venv_dir, _ = _get_venv_data(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): if not value: ctx.abort() def _show_package(package): return package 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/`, update pip, installs the package, and links the package's scripts to `~/.local/bin/`. """ pass @cli.command(context_settings=CONTEXT_SETTINGS) def version(): """Show version and exit.""" package_version = _get_version(__name__) click.echo(package_version) @cli.command('list', context_settings=CONTEXT_SETTINGS) def list_installed(): """List installed packages.""" click.echo('Installed:') for package in sorted(os.listdir(VENV_ROOT_DIR)): package_version = _get_version(package) click.echo(' - {} ({})'.format(package, package_version)) @cli.command(context_settings=CONTEXT_SETTINGS) def freeze(): """Output installed packages in requirements format.""" for package in sorted(os.listdir(VENV_ROOT_DIR)): package_version = _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=_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 install(requirement, name): """ 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 (requirement or name): raise click.UsageError('missing arguments/options') # check mutually esclusive args if requirement and name: raise click.UsageError('too much arguments/options') # populate packages list with req file if requirement: try: with open(requirement, 'r') as req: name = map(str.strip, req.readlines()) except IOError: raise click.FileError(requirement) # process packages with click.progressbar(name, label='Installing', item_show_func=_show_package) as packages: for package in packages: venv_dir, venv_py = _get_venv_data(package) cmd = [venv_py, '-m', 'pip', 'install', '--quiet'] # create venv if not exists if not os.path.isdir(venv_dir): create(venv_dir, symlinks=True, with_pip=True) # upgrade pip in venv cmd.extend(['--upgrade', 'pip']) check_call(cmd) # install package in venv cmd.append(package) check_call(cmd) # create scripts symlink scripts = _get_console_scripts(package) for script in scripts: script_name = script.split('/')[-1] link = os.path.join(BIN_DIR, script_name) if not os.path.islink(link): os.symlink(script, link) else: click.secho(' is already installed, skip', fg='green') @cli.command(context_settings=CONTEXT_SETTINGS, short_help='Update packages.') @click.option('-y', '--yes', is_flag=True, callback=_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 update(requirement, name): """ 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. """ # check mutually esclusive args if requirement and name: raise click.UsageError('too much arguments/options') # populate packages list with req file if requirement: try: with open(requirement, 'r') as req: name = map(str.strip, req.readlines()) except IOError: raise click.FileError(requirement) # populate packages list with all currently installed if not name: name = os.listdir(VENV_ROOT_DIR) with click.progressbar(name, label='Updating', item_show_func=_show_package) as packages: for package in packages: venv_dir, venv_py = _get_venv_data(package) if not os.path.isdir(venv_dir): click.secho(' is not installed, skip', fg='yellow') continue cmd = [venv_py, '-m', 'pip', 'install', '--quiet'] # upgrade pip in venv cmd.extend(['--upgrade', 'pip']) check_call(cmd) # install package in venv cmd.extend(['--upgrade', package]) check_call(cmd) # update scripts symlink scripts = _get_console_scripts(package) for script in scripts: script_name = script.split('/')[-1] link = os.path.join(BIN_DIR, script_name) if not os.path.islink(link): os.symlink(script, link) @cli.command(context_settings=CONTEXT_SETTINGS, short_help='Uninstall packages.') @click.option('-y', '--yes', is_flag=True, callback=_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(requirement, name): """ 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 (requirement or name): raise click.UsageError('missing arguments/options') # check mutually esclusive args if requirement and name: raise click.UsageError('too much arguments/options') # populate packages list with req file if requirement: try: with open(requirement, 'r') as req: name = map(str.strip, req.readlines()) except IOError: raise click.FileError(requirement) with click.progressbar(name, label='Removing', item_show_func=_show_package) as packages: for package in packages: venv_dir = os.path.join(VENV_ROOT_DIR, package) if os.path.isdir(venv_dir): # remove scripts symlink scripts = _get_console_scripts(package) for script in scripts: script_name = script.split('/')[-1] link = os.path.join(BIN_DIR, script_name) if os.path.islink(link): os.remove(link) # remove package venv rmtree(venv_dir) else: click.secho(' is not installed, skip', fg='yellow') if __name__ == '__main__': cli() PK!H"!#&pipis-1.2.0.dist-info/entry_points.txtN+I/N.,()*,,V9\\PK!HǎASSpipis-1.2.0.dist-info/WHEEL A н#J@Z|Jmqvh&#hڭw fi4WZ^EgM_-]0(q7PK!H\NGpipis-1.2.0.dist-info/METADATAWn68H7,,)^&j48]7dLKkHʮ {=؞dlG]40}x߹5Z3Pd%wSVb*^qm/&hR%M&uav(?0OãtWlA k+O͒L)ϔ`lt2xS'H6qBU%J lm߇sBVҳϑF Ec:(3t fޡ+]2KYs Z47McA^rɍ̵̪ p#KMZUS\vN 0)N\n߈"#bGpeNf:̝ߖ$Es[%[Ko*jgmJSc;0:ܹqDOa=xdnjߓSg1{+19 ϸGtڅ/ZvD߃uD Ic(Np9U5O^V0~<9ÄF7`_Eքtך[54:L[S|RsY=\Uf;*|'SǙ)䙷J3Q%\(Su&0=H|6BиXr,Zhք3IPsB! V܍Y!v; *0X1 QވRp`I)T%4`B:sȒrӉ|Z(#.a{11u;+y骤uU .7PȵtYRPsXM#~"s-f6`m2G "DfӎB $^6(yhL {(r!6t?OܖKv Et:W!.oCyw;,_Q"߻A=opRЖ_HtRT8ϯDJ nE ȮH_iD(PTAqio_b4>N'p4zzpz 'IBajTS h%#EH'q]DF% 6ҾRK*`n T߲Vsn`mon`bp/ѱrmL"*ׁ||Mzzه&j5'.|%ï1l억>&D>3&!F2ۮj3Aƍ킏Xm3p^*ñXH4l'n ̓+CAG?PK!\aU'U'pipis/__init__.pyPK!H"!#&'pipis-1.2.0.dist-info/entry_points.txtPK!HǎASS'pipis-1.2.0.dist-info/WHEELPK!H\NGu(pipis-1.2.0.dist-info/METADATAPK!Hp.pipis-1.2.0.dist-info/RECORDPKr/