viv/README.md

4.4 KiB

Viv

Logo

viv isn't venv


See usage for more demo gifs.

Setup

Start by cloning the repo and symlinking the script for access to the CLI. By default it will symlink ./src/viv/viv.py to ~/bin/viv. You can set PREFIX to symlink to a different location.

git clone git@github.com:daylinmorgan/viv.git ~/.viv
cd ~/.viv
make install # or PREFIX=~/.local/bin make install

Place this directory on the python path in your rc file.

export PYTHONPATH="$PYTHONPATH:$HOME/.viv/src"
pip install viv

Usage

Then in any python script with external dependencies you can add this line, to automate env creation and installation of dependencies.

__import__("viv").activate("click")

To remove all vivenvs:

viv remove $(viv list -q)

Standalone Function

The below function can be freely pasted at the top of your scripts and requires no modification of your PYTHONPATH or import of additional modules.

It can be auto-generated with for example: viv freeze rich --standalone.

The only part necessary to modify if copied verbatim from here is the call to _viv_activate.

# <<<<< auto-generated by daylinmorgan/viv (v.22.12a3)
# fmt: off
def _viv_activate(*pkgs: str, track_exe: bool = False, name: str = "") -> None: # noqa
    i,m,e = __import__,map,lambda x: True if x else False # noqa
    if not {*m(type, pkgs)} == {str}: raise ValueError(f"spec: {pkgs} is invalid") # noqa
    ge,sys,P,exe = i("os").getenv,i("sys"),i("pathlib").Path,i("sys").executable # noqa
    vivcache = (P(ge("XDG_CACHE_HOME",P.home()/".cache"))/"viv"/"venvs");vivcache.mkdir(parents=True, exist_ok=True) # noqa
    spec,exe_path = [*pkgs], (str(P(exe).resolve()) if track_exe else "N/A") # noqa
    hash = i("hashlib").sha256(); hash.update(str(spec).encode()) # noqa
    if track_exe: hash.update(exe_path.encode()) # noqa
    _id = hash.hexdigest() # noqa
    name, envpath = (lambda n: (n,vivcache/n))(name if name else _id) # noqa
    if name not in (d.name for d in vivcache.iterdir()) or ge("VIV_FORCE"): # noqa
        run = i("subprocess").run; i("venv").EnvBuilder(with_pip=True, clear=True).create(envpath) # noqa
        with (envpath/"pip.conf").open("w") as f:f.write("[global]\ndisable-pip-version-check = true") # noqa
        p = run([envpath/"bin"/"pip","install","--force-reinstall", *spec], universal_newlines=True, # noqa
            **dict(zip(("stdout","stderr"),[(-1,-2),(None,)*2,][e(ge("VIV_VERBOSE"))]))) # noqa
        if (p.returncode!=0)*envpath.is_dir():i("shutil").rmtree(str(envpath)) # noqa
        if p.returncode!=0:sys.stderr.write(f"pip had non zero exit ({p.returncode})\\n{p.stdout}");sys.exit(p.returncode) # noqa
        with (envpath/"viv-info.json").open("w") as f: # noqa
            i("json").dump({"created":str(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe},f) # noqa
    sys.path = [p for p in (*sys.path, str(*(envpath/"lib").glob("py*/si*"))) if p!=i("site").USER_SITE] # noqa

_viv_activate("markdown-it-py==2.2.0", "mdurl==0.1.2", "Pygments==2.14.0", "rich==13.3.2") # noqa

# fmt: on
# >>>>> code golfed with <3

Alternatives

pip-run

pip-run (10.0.5)
├── autocommand (2.2.2)
├── jaraco-context (4.3.0)
├── jaraco-functools (3.6.0)
│   └── more-itertools (9.1.0)
├── jaraco-text (3.11.1)
│   ├── autocommand (2.2.2)
│   ├── inflect (6.0.2)
│   │   └── pydantic>=1.9.1 (1.10.5)
│   │       └── typing-extensions>=4.2.0 (4.5.0)
│   ├── jaraco-context>=4.1 (4.3.0)
│   ├── jaraco-functools (3.6.0)
│   │   └── more-itertools (9.1.0)
│   └── more-itertools (9.1.0)
├── more-itertools>=8.3 (9.1.0)
├── packaging (23.0)
├── path>=15.1 (16.6.0)
├── pip>=19.3 (23.0.1)
└── platformdirs (3.1.0)

pipx

pipx (1.1.0)
├── argcomplete>=1.9.4 (2.1.1)
├── packaging>=20.0 (23.0)
└── userpath>=1.6.0 (1.8.0)
    └── click (8.1.3)