Emacs + Python + uv
Python has yet another packaging tool, uv [fn]. Here’s an opinionated
configuration for uv based projects that plays well with Emacs. It
uses a .dir-locals.el approach to make the config project specific,
so you can drop it into a project without impacting any existing
Python config.
The configuration and tooling includes:
- Test runner (pytest)
 - Linting (ruff)
 - Syntax / type-checking (Eglot + pyright)
 - Python shell (IPython)
 - Org babel
 
Create a new project
Create a new Python project using uv, we’lll call it example and
then cd into it:
uv init example
cd example
uv prepopulates the project with some basics:
tree
.
├── hello.py
├── pyproject.toml
├── README.md
└── uv.lock
ruff
ruff is astral’s linter; similar to uv it’s written in Rust and is
the fastest in its class.
Add ruff to the dev dependencies, and give your project a first lint:
uv add --dev ruff
uv run ruff check
See the tutorial for next steps configuring ruff.
pytest
Add pytest to the project’s dev dependencies and run Python tests
using the pytest runner:
uv add --dev pytest
uv run pytest
pytest integrates smoothly with Emacs via the python-pytest
package. Configure it by setting the python-pytest-executable
variable in .dir-locals.el (create the file under your project root
if it doesn’t already exit):
(python-mode . ((python-pytest-executable . "uv run pytest")))
ipython
Add ipython to the project’s dev dependencies and start the
ipython console by running:
uv add --dev ipython
uv run ipython
To have Emacs use this project’s IPython as the default interpreter,
set the python-shell-interpreter and python-shell-interpreter-args
vars using .dir-locals.el:
(nil . ((python-shell-interpreter . "uv")
        (python-shell-interpreter-args
         . "run ipython -i --simple-prompt --InteractiveShell.display_page=True")))
pyright
The pyright language server provides an LSP server.
uv add --dev pyright
You won’t typically start the pyright language server directly;
eglot will start it before trying to connect. Here is the
.dir-locals to configure eglot:
((python-mode
  . ((eval . (add-to-list
            'eglot-server-programs
            `((python-mode python-ts-mode) .
              ,(eglot-alternatives
                '(("uv" "run" "pyright-langserver" "--stdio")))))))))
Putting it all together
The .dir-locals.el should look like:
((python-mode
  . ((python-pytest-executable . "uv run pytest")
     (eval . (add-to-list
             'eglot-server-programs
             `((python-mode python-ts-mode) .
               ,(eglot-alternatives
                 '(("uv" "run" "pyright-langserver" "--stdio"))))))))
 (org-mode . ((org-babel-python-command . "uv run python")))
 (nil . ((python-shell-interpreter . "uv")
         (python-shell-interpreter-args
          . "run ipython -i --simple-prompt --InteractiveShell.display_page=True"))))