Skip to content

Commit 725cd13

Browse files
committed
add pipx to run scripts/entrypoints; add documentation
1 parent 4af3425 commit 725cd13

File tree

3 files changed

+71
-19
lines changed

3 files changed

+71
-19
lines changed

README.md

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</p>
66

77
<a href="https://pypi.python.org/pypi/pythonloc/">
8-
<img src="https://img.shields.io/badge/pypi-0.1.1.2-blue.svg" /></a>
8+
<img src="https://img.shields.io/badge/pypi-0.1.2.0-blue.svg" /></a>
99

1010
**pythonloc** is a drop in replacement for `python` and `pip` that automatically recognizes a `__pypackages__` directory and prefers importing packages installed in this location over user or global site-packages. If you are familiar with node, `__pypackages__` works similarly to `node_modules`.
1111

@@ -15,6 +15,8 @@ This is an alternative to using Virtual Environments.
1515

1616
This is a Python implementation of [PEP 582](https://www.python.org/dev/peps/pep-0582/), "Python local packages directory". The goal of pythonloc is to make an accessible tool while discussion takes place around adding this functionality to CPython itself. If you prefer, you can [build your own CPython](https://github.com/kushaldas/cpython/tree/pypackages) with these changes instead of using `pythonloc`.
1717

18+
**Please note that PEP 582 has not been accepted. It may or not be accepted in the long term. `pythonloc` is experimental and its API may change in the future.**
19+
1820
## Testimonials
1921

2022
*Featured on [episode #117](https://pythonbytes.fm/episodes/show/117/is-this-the-end-of-python-virtual-environments) of the Python bytes podcast.*
@@ -41,7 +43,7 @@ or
4143
```
4244
python3 -m pip install --user pythonloc
4345
```
44-
you will have three CLI tools available to you: **pythonloc**, **piploc**, and **pipfreezeloc**.
46+
you will have four CLI tools available to you: **pythonloc**, **piploc**, **pipx**, and **pipfreezeloc**.
4547

4648
### pythonloc
4749
Short for "python local", it is a drop-in replacement for python with one important difference: the local directory `__pypackages__/<version>/lib` is added to the front of `sys.path`. `<version>` is the Python version, something like `3.7`. All arguments are forwarded to `python`.
@@ -57,6 +59,8 @@ you would run
5759
pythonloc ...
5860
```
5961

62+
If PEP 582 is adopted, `python` itself will have this behavior.
63+
6064
### piploc
6165
Short for "pip local", it invokes pip with the same `sys.path` as `pythonloc`. If installing a package, the target installation directory is modified to be `__pypackages__` instead of the global `site-packages`.
6266

@@ -75,6 +79,31 @@ you would run
7579
piploc ...
7680
```
7781

82+
If PEP 582 is adopted, I think `pip` should default to working in the appropriate `__pypackages__` directory. A flag can be added to install to site-packages, if desired.
83+
84+
### pipx
85+
Installing packages that have so called "entry points" to `__pypackages__` presents a problem. The entry points, or "binaries", are no longer available on your $PATH as they would be if you installed in a virtual environment or to your system. These binaries are massively popular and useful. Examples of binaries are `black`, `pytest`, `tox`, `flake8`, `mypy`, `poetry`, and `pipenv` (and indeed `pythonloc` itself).
86+
87+
`pipx` is a binary installer and runner for Python that, when run, searches for a binary in the appropriate `__pypackages__` location and runs it. If you are familiar with JavaScript's [`npx`](https://www.npmjs.com/package/npx), it's similar to that.
88+
89+
So instead of running
90+
```
91+
BINARY [BINARY ARGS]
92+
```
93+
you would run
94+
```
95+
pipx run BINARY [BINARY ARGS]
96+
```
97+
If not found, pipx will install and run it from a temporary directory. If you require the binary to be found in the `__pypackages__` directory, you can run
98+
```
99+
pipx run --pypackages BINARY [BINARY ARGS]
100+
```
101+
If the binary is not found, and error will be presented.
102+
103+
Something to note here: When installing a new package to an existing `__pypackages__` directory, the entry points will not be created in `.../3.6/lib/bin`, for example, if something is already there. To do that, you need to run `piploc install -U PACKAGE`. When you do that, the entire contents of the directory will be replaced. Fixing this would require a modification to `pip` itself.
104+
105+
If PEP 582 is adopted, `pipx` will be a good companion tool to run binaries.
106+
78107
### pipfreezeloc
79108
Running `pip freeze` presents a problem because it shows all installed python packages: those in `site-packages` as well as in `__pypackages__`. You likely only want to output the packages installed to `__pypackages__` and that is exactly what `pipfreezeloc` does.
80109

@@ -93,6 +122,8 @@ you would run
93122
pipfreezeloc > requirements.txt
94123
```
95124

125+
If PEP 582 is adopted, a more robust solution to freezing the state of `__pypackages__` should be created.
126+
96127
## Installing from requirements.txt/Lockfiles
97128
This works just like it does in pip. You just need a `requirements.txt` file to install from.
98129

@@ -121,10 +152,8 @@ piploc install -r requirements.txt
121152
pythonloc <app>
122153
```
123154

124-
## Exporting to Lockfiles
125-
```
126-
pipfreezeloc > requirements.txt
127-
```
155+
In the long term tools will be able to install directly to `__pypackages__` or piploc will be able to read various lockfile formats.
156+
128157

129158
## Examples
130159

@@ -177,14 +206,36 @@ Successfully installed certifi-2018.11.29 chardet-3.0.4 idna-2.8 requests-2.21.0
177206
Successfully uninstalled requests-2.21.0
178207
```
179208

209+
## Entry Points / Binaries
210+
```
211+
> piploc install cowsay
212+
Collecting cowsay
213+
Using cached https://files.pythonhosted.org/packages/e7/e7/e93f311adf63ac8936beb962223771b1ab61227ae3d9ec86e8d9f8f9da1c/cowsay-2.0-py2.py3-none-any.whl
214+
Installing collected packages: cowsay
215+
Successfully installed cowsay-2.0
216+
217+
> pipx run cowsay moooo from local __pypackages__!
218+
________________________________
219+
< moooo from local __pypackages__! >
220+
================================
221+
\
222+
\
223+
^__^
224+
(oo)\_______
225+
(__)\ )\/ ||----w |
226+
|| ||
227+
228+
229+
```
230+
180231
## Downsides?
181232

182233
While this PEP is pretty exciting, there are a some things it doesn't solve.
183234

184-
* entrypoints: when you install a package, any entry points a package may have in the `bin` folder (like `black` or `tox`) are not accessible based on this PEP. [pipx](https://github.com/pipxproject/pipx) (also my project), is the perfect tool to search in `__pypackages__/3.6/lib/bin` for the entry point you want to run. So you would run `pipx run tox` and it would locate `__pypackages__/3.6/lib/bin`. (It doesn't currently do this.) This is very similar to [npx](https://www.npmjs.com/package/npx), which will search in `node_modules/bin`.
185235
* OS-dependent packages: The directory structure in `__pypackages__` is namespaced on python version, so packages for Python 3.6 will not mix with 3.7, which is great. But sometimes packages install differently for different OS's, so Windows may not match mac, etc.
186-
* site-packages: This PEP first looks to `__pypackages__` but will fall back to looking in `site-packages`. This is not entirely hermetic and could lead to some confusion around which packages are being used. I would prefer the search path be **only** `__pypackages__` and nothing else.
187-
* perceived downside -- bloat: Many have brought this up in various forums, comparing it to `node_modules`, but I don't think it applies here. For one, the same if not more "bloat" is installed into a virtual environment, so this just moves it into a local directory. No additional bloat. In fact, it is more obvious and can be deleted because it's not hidden away in a virtual env directory. But more importantly, I think the assumption that it is bloated or will be abused stems from JavaScript's ecosystem. JavaScript has a notoriously limited standard library, and developers need to reach for third party packages more often. In addition, the JavaScript development heavily relies on many plugins and transpilation, something Python does not. I do not find the bloat argument convincing.
236+
* site-packages: This PEP first looks to `__pypackages__` but will fall back to looking in `site-packages`. This is not entirely hermetic and could lead to some confusion around which packages are being used. I would prefer the default search path be **only** `__pypackages__` and nothing else.
237+
* perceived downside -- bloat: Many have brought this up in various forums, comparing it to `node_modules`, but I don't think it applies here. For one, the same if not more content is installed into a virtual environment, so this just moves it into a local directory. No additional bloat. In fact, it is more obvious and can be deleted because it's not hidden away in a virtual env directory. But more importantly, I think the assumption that it is bloated or will be abused stems from JavaScript's ecosystem. JavaScript has a notoriously limited standard library, and developers need to reach for third party packages more often. In addition, the JavaScript community heavily relies on many plugins and transpilation. Python does not. I do not find the bloat argument convincing.
238+
* Some pip installation idiosyncracies. For example, `pip install` with `--target` will wipe out content in the `lib/bin` directory when the `-U` flag is passed, but not put anything there when it's not passed.
188239

189240
## FAQ
190241

pythonloc/pythonloc.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ def _get_script_path():
4040
return None
4141

4242

43-
def _get_pip_target_args(pip_args):
43+
def _get_pip_install_args(pip_args):
4444
if "install" in pip_args:
45-
if "--target" not in pip_args:
46-
# use target dir if installing
47-
target = ["--target", _get_pypackages_lib_path()]
45+
target = [
46+
"--target",
47+
_get_pypackages_lib_path(),
48+
]
49+
4850
if (
4951
pip.__version__.startswith("9.") or pip.__version__.startswith("10.")
5052
) and "--system" not in pip_args:
@@ -62,8 +64,8 @@ def pythonloc():
6264

6365
def piploc():
6466
pip_args = sys.argv[1:]
65-
target = _get_pip_target_args(pip_args)
66-
args = [sys.executable] + ["-m", "pip"] + pip_args + target
67+
install_args = _get_pip_install_args(pip_args)
68+
args = [sys.executable] + ["-m", "pip"] + pip_args + install_args
6769
os.execve(sys.executable, args, _get_env())
6870

6971

setup.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111

1212
import io
1313
import os
14-
import sys
15-
from setuptools import find_packages, setup, Command
14+
from setuptools import find_packages, setup
1615

17-
DEPENDENCIES = []
16+
DEPENDENCIES = ["pipx>=0.12.2.0"]
1817
EXCLUDE_FROM_PACKAGES = ["contrib", "docs", "tests*"]
1918
CURDIR = os.path.abspath(os.path.dirname(__file__))
2019

@@ -23,7 +22,7 @@
2322

2423
setup(
2524
name="pythonloc",
26-
version="0.1.1.2",
25+
version="0.1.2.0",
2726
author="Chad Smith",
2827
author_email="[email protected]",
2928
description="Run Python using packages from local directory __pypackages__",

0 commit comments

Comments
 (0)