Isolated Execution of Python Command-Line Applications Using pipx
pipx enables installation and execution of Python-based terminal utilities in dedicated environments, exposing their entry points system-wide while avoiding dependency conflicts. It is suited for tools published on PyPI that provide console scripts.
Core capabilities:
- Installs each package into its own virtual environment.
- Exposes executables globally without polluting the system Python environment.
- Lists, upgrades, and removes managed tools.
- Runs a tool in a temporary disposable environment.
- Requires Python ≥3.6 and
pippresent.
Setting Up pipx
Install it system-wide:
python -m pip install pipx
Ensure the wrapper directory is on PATH:
pipx ensurepath
Verify installation:
pipx list
Initially, output indicates no packages are managed.
Installing a Tool
Example: globally install a console utility named animal_printer:
pipx install animal_printer
This creates an isolated env, installs the package, and links the executable. Test it:
animal_printer moon
List managed tools again:
pipx list
Default locations:
- Environments:
~/.local/pipx/venvs(override viaPIPX_HOME). - Binaries:
~/.local/bin(override viaPIPX_BIN_DIR).
Keeping Tools Updated
Upgrade a specific tool:
pipx upgrade animal_printer
Upgrade all managed tools:
pipx upgrade-all
Run Without Installing
Execute a tool from a transient environment:
pipx run py_animal_printer moooo
Run from a remote script:
pipx run https://example.com/scripts/demo.py
Removing Tools
Uninstall a specific tool:
pipx uninstall animal_printer
Remove all:
pipx uninstall-all
Help Reference
pipx --help
Dependency and Build Management with Poetry
Poetry offers a unified approach for managing project metadata, dependencies, and packaging using pyproject.toml. It supersedes setup.py/requirements.txt workfolws and integrates tightly with modern Python tooling.
Installing Poetry via pipx
pipx install poetry
Confirm:
poetry --version
Update later via:
pipx upgrade poetry
Configuration Management
View all settings:
poetry config list
Inspect one key:
poetry config virtualenvs.path
Set a value:
poetry config virtualenvs.in-project true
Unset a key:
poetry config virtualenvs.path --unset
Exploring Packages
List locked dependencies:
poetry show
Show details for a package:
poetry show httpx
Options:
--no-dev: omit dev dependencies.--tree: hierarchical view.--latest/--outdated: show newer versions.
Project Workflow with Poetry
Scaffold New Project
Generate a conventional layout:
poetry new demo_lib --src
Structure:
demo_lib
├── pyproject.toml
├── README.rst
├── src
│ └── demo_lib
│ └── __init__.py
└── tests
├── __init__.py
└── test_demo_lib.py
Use --name to customize the distribution name.
Initialize Existing Codebase
In project root:
poetry init
Answer prompts to produce pyproject.toml. Example result:
[tool.poetry]
name = "demo_lib"
version = "0.1.0"
description = ""
authors = ["Dev <dev@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Install Dependencies
poetry install
With existing poetry.lock, exact versions are used. Otherwise, resolution occurs and lock file is created.
Parameters:
--no-dev: skip dev group.--remove-untracked: purge stale entries from lock.-E extra1: include extras.--no-root: skip installing the project itself.
Enable in-project virtualenvs:
poetry config virtualenvs.in-project true
Or per-project poetry.toml:
[virtualenvs]
in-project = true
Activate:
source .venv/bin/activate
Update Dependencies
Refresh all:
poetry update
Target specific ones:
poetry update httpx toml
Options: --dry-run, --no-dev, --lock.
Add Packages
Add runtime requirement:
poetry add httpx pendulum
Specify version:
poetry add pendulum@^2.0.5
Latest:
poetry add pendulum@latest
From VCS:
poetry add git+https://github.com/user/pendulum.git#branch
Local path/wheel:
poetry add ./local_pkg/ dist/pkg-0.1.0-py3-none-any.whl
Editable install:
[tool.poetry.dependencies]
my_pkg = { path = "../my_pkg", develop = true }
Options: -D for dev deps, --optional, --dry-run, --lock.
Remove Packages
poetry remove pendulum
Flags: -D, --dry-run.
Execute Commands in Environment
poetry run python -V
Define script entry in pyproject.toml:
[tool.poetry.scripts]
start_tool = "demo_lib.cli:entry"
Invoke:
poetry run start_tool
Validation and Utilities
Check syntax:
poetry check
Search repository:
poetry search requests
Lock versions:
poetry lock
Show current project version:
poetry version
Export to requirements.txt:
poetry export -f requirements.txt --output requirements.txt
Build source/wheel:
poetry build
Publish:
poetry publish --username xxx --password yyy
Common Scenarios
Mirror Configuration for Faster Resolution
Add a mirror and make it default:
[[tool.poetry.source]]
name = "tsinghua"
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"
default = true
Managing Development-Only Dependencies
Install test framework:
poetry add -D pytest
Run inside venv:
poetry run pytest
Usage of poetry.lock
Commit poetry.lock for reproducible environments across teams or deployments. It pins exact versions regardless of caret constraints in pyproject.toml. Use poetry update to refresh lock when needed.
Defining Console Entry Points
Example mapping:
[tool.poetry.scripts]
run_demo = "demo_lib.main:launch"
After poetry install, run_demo becomes an executable command.
Sample Project Structure
proj_dir
├── pyproject.toml
├── README.rst
├── src
│ └── demo_lib
│ ├── __init__.py
│ └── main.py
└── tests
├── __init__.py
└── test_demo_lib.py
pyproject.toml example:
[tool.poetry]
name = "demo_lib"
version = "0.1.0"
description = ""
authors = ["Dev <dev@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
[tool.poetry.scripts]
run_demo = "demo_lib.main:launch"
[[tool.poetry.source]]
name = "tsinghua"
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"
default = true
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
main.py:
import datetime
import time
import os
def greet():
print("Hello world")
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
def launch(**kw):
greet()
print("CWD:", os.getcwd())
time.sleep(10)
print("Finished after 10s")
if __name__ == "__main__":
launch()
Install and activate:
poetry install
source .venv/bin/activate
Run via defined script:
run_demo