Greetings fellow Pythonistas and Emacs users!
Have you ever worked on a project that uses one of the many Python package managers and/or virtual environments, where all the linters, formatters and commit hooks are set up meticulously, and then when you fire up Emacs, packages like flycheck or lsp-mode are either unable to find the binary in your virtualenv, or are using the wrong one?
Have you ever tried one of the 11+ Emacs virtualenv packages to help you fix this problem, but are still at a loss as to why your other favorite Emacs packages still can't find the right binaries, or they stop working when you switch to a different project using a different flavor of virtualenv?
If you answer "yes" for any of these questions, you've come to the right place.
The first key insight is to recognize the paths to the executables of many Python linting and formatting Emacs packages are configurable.
The second key insight is Emacs allows you to setup a different value for the executable path on a per buffer basis, and that these packages work with these buffer-local values.
The hardest problem is finding the correct executable, this is what
pet
tries to solve.
pet
solves this through automatic detection and configuration:
- Detect your project
- Uses Projectile or built-in
project.el
to find your project root - Find your virtualenv
- Searches for virtualenvs using multiple methods: configuration files
(
pyproject.toml
,Pipfile
, etc.), standard directories (.venv
,venv
), environment variables (VIRTUAL_ENV
), and tool-specific detection - Set executable paths
- Once a virtualenv is found, pet sets buffer-local variables so your
Emacs packages (flycheck, lsp-mode, formatters, etc.) use the correct
executables from that virtualenv's
bin
directory - Remote file support
- For Python files accessed via TRAMP, pet detects remote virtualenvs and configures executables with proper state isolation to prevent cross-project interference
This happens automatically with zero Emacs configuration necessary. The detection runs once per project and results are cached for performance.
- poetry, pipenv
- conda, mamba, micromamba
- uv, pdm
- venv, virtualenv
- direnv, pixi
- pre-commit, pipx, pyenv, docker, maturin, virtualenvwrapper, and more
See doc/SUPPORTED.rst for the complete list and integration notes.
- lsp-mode, eglot, flycheck
- lsp-jedi, lsp-pyright
- ruff-format, python-black, python-isort
- python-pytest, pytest-el
- dape, dap-python
- Built-in project.el, projectile
- Built-in TRAMP for remote file editing
- envrc, apheleia, format-all, and more
See doc/SUPPORTED.rst for the complete list and integration caveats.
pet
has minimal system requirements and requires Emacs 27.1+.
For TOML and YAML configuration file parsing, pet
supports multiple
options:
- tomlparse.el for TOML (requires Emacs 29+)
- yaml.el for YAML (works with Emacs 26.1+)
pet
will automatically detect and use the first available parser for
each format, preferring external programs for performance, then falling
back to pure Emacs Lisp implementations.
If you are using Emacs < 29, the sqlite3
command is required for pre-commit
database parsing.
# Install dasel (cross-platform)
# macOS
brew install dasel
# Linux (various package managers)
sudo apt install dasel # Ubuntu/Debian
sudo dnf install dasel # Fedora
sudo pacman -S dasel # Arch Linux
Alternatively, install tomlparse.el and yaml.el via MELPA:
M-x package-install RET tomlparse RET
Or:
M-x package-install RET yaml RET
If you are using Emacs on macOS, install exec-path-from-shell first to ensure
Python tools are available in your exec-path
.
(use-package pet
:config
(add-hook 'python-base-mode-hook 'pet-mode -10))
This automatically configures all supported packages for both
python-mode
and python-ts-mode
.
For projects using conda, mamba, or pixi, you can now switch environments interactively:
M-x pet-conda-switch-environment M-x pet-mamba-switch-environment M-x pet-pixi-switch-environment
When you enable pet-mode
on a fresh project using these tools,
pet
will automatically prompt you to select an environment if none
is currently active.
For packages pet
doesn't yet support, or when you need fine-grained control:
(add-hook 'python-mode-hook
(lambda ()
(setq-local python-shell-interpreter (pet-executable-find "python")
python-shell-virtualenv-root (pet-virtualenv-root))))
For flycheck setup: (add-hook 'python-mode-hook 'pet-flycheck-setup)
See doc/SUPPORTED.rst for a complete configuration example with lsp-mode, flycheck, formatters, and testing tools.
pet
caches virtualenv detection results and works efficiently on
most projects. For large projects or performance issues, see
doc/PERFORMANCE.rst for detailed optimization strategies.
For configuration options including file search methods, external tool settings, parser selection, and project-specific settings, see doc/CUSTOMIZATION.rst.
pet
doesn't create virtualenvs - create your virtualenv and install
dependencies first, then pet will detect it automatically.
Enable debug mode with (setq pet-debug t)
and watch the
*Messages*
buffer. Use M-x pet-verify-setup
in your Python
buffers to see what was detected.
Verify setup for specific packages:
lsp-mode
:M-x lsp-describe-session
eglot
:M-x eglot-show-workspace-configuration
flycheck
:M-x flycheck-verify-setup
Install fd
for faster file searches: brew install fd
(macOS) or
sudo apt install fd-find
(Ubuntu). See doc/PERFORMANCE.rst for
optimization strategies.
Use envrc instead of other direnv packages. See doc/SUPPORTED.rst for detailed integration notes.
Many Python projects use development tools in different virtualenvs
(e.g., pre-commit hooks). Managing multiple virtualenv paths in
exec-path
reliably is complex. Using absolute executable paths is
simpler and more performant.
No, you can uninstall them all - this is the main purpose of pet.