🏠 About Now Archive Tags RSS

Use virtual environment in Emacs' Python Mode if in a project with a venv

Today, I was going to work on a python project for half an hour at the end of my work day. I tried to find a good python module to use to work with RSS and Atom feeds. I found one and installed it in my virtual environment and pasted in some example code in a buffer in Emacs. I wanted to run the code in the python shell with Emacs' handy C-c C-c keyboard shortcut. So I opened the shell with C-c C-p and then hit C-c C-c, but I got an error that the shell did not understand the import on line 1 where I was importing the module I just installed in my virtualenv. I realised the shell ran from outside the virtualenv. I had not looked into this before, but found there were various packages people recommended to fix this. I tried one that looked interesting, but it did not work. Maybe because I was on my work laptop with Windows at the time. I tried a few things, but soon gave up.

I then discovered .dir-locals.el files was a possible solution. It gives buffer-local values to variables inside the folder it is placed for the mode that you configure through an alist. The first suggestion I found was to use a variable that did not work at all. I wasted a lot of time trying to get it to work. When I was close to giving up, I discovered that I could use the python-shell-interpreter variable in the .dir-locals.el file and it worked. If you launch the python shell from the python within the virtual environment, then the shell also sees all the other packages installed in that virtual environment.

I then thought that the solution I had found would not work on my own machines that run GNU/Linux since the python.exe-file neither exists nor the folder it is in in virtual environments on my preferred platform. A .dir-locals.el-file inside a project checked in with git is not a particularly cross-platform solution to this problem for this reason and it would be a hassle to make one for every python project with a virtual environment. I thought I could make a function in my Emacs configuration that would check which platform I was on, whether I was in a project or not and whether a directory with the name venv exists (I tend to use that name for virtual environments) and then just set the variable to the correct path based on that.

When I got home, after dinner, I made that function. I love how well-documented Emacs is. It makes doing things like this really easy. I just looked up function names starting with project and found something useful after reading up on a few. A bit of evaluation in different buffers with C-x C-e to check if things worked the way I thought and a bit of tweaking when it turned out that cddr did not return the same as nth 2 (cddr returned a list with a string, but nth 2 returned a string) and then I ended up with this function:

(defun emo-python-virtualenv ()
  "Sets the python interpreter to python in the venv if in a project and a venv exists."
  (when (project-current)
    (let ((pythonpath (concat
                       (nth 2 (project-current)) (if (eq system-type 'gnu/linux)
                                                     "venv/bin/python"
                                                   "venv/Scripts/python.exe"))))
      (when (file-exists-p pythonpath)
        (setq-local python-shell-interpreter pythonpath)))))

To put it to work every time I open a python file, I also needed to add it to python-mode-hook like this in my configuration for Python mode:

(add-hook 'python-mode-hook 'emo-python-virtualenv)

When I now try to import packages that only exists in a virtual environment into the python shell, it works if I loaded the shell from a file within the same project as the virtual environment, but not from a file from outside that project. If I load the shell from a file within a project without a venv, I get the system's Python and its available packages. No need for outdated packages that doesn't work or directory-local variables that are platform dependent. This works on Windows as well. It's easy and fun to fix things like this in Emacs, and I learned some Elisp and a bit about Python virtual environments in the process. It would be convenient if things like these worked out of the box, but that would demand a bit more code since my function works on the basis of the directory name venv for every virtual environment. If I can find a way to do this that is not dependent on the name of the virtual environment folder, that would be even better.

© Einar Mostad 2010 - 2026. Content is licensed under the terms of CC BY SA except code which is GNU GPL v3 or later.
Made with GNU Emacs, Org-Static-Blog, and Codeberg Pages on GNU/Linux.