Details for python virtual environments

This also fixes a bug when using LSP for Python projects.
This commit is contained in:
Howard Abrams 2026-01-02 14:25:13 -08:00
parent 9f2eb65d06
commit bc5a1c8ea2
3 changed files with 116 additions and 39 deletions

View file

@ -136,7 +136,14 @@ Next, after reading David Vujics [[https://davidvujic.blogspot.com/2025/03/ar
# c.InteractiveShellApp.exec_lines = ['%autoreload 2']
#+END_SRC
** Virtual Environment
** Isolated Python Environments
While the Python community (and my work at my company) had difficulty transitioning from Python 2 to 3, I often run into issues needing a particular Python version and modules. After playing around with different approaches, Im finding:
* Docker environments are nicely isolated, but annoying to work from outside the container
* The Builtin =venv= is works well for different library modules, but not for different versions
* The =pyenv= deals with different Python versions, but is overkill for library isolation
While the [[https://github.com/marcwebbie/auto-virtualenv][auto-virtualenv]] project attempts to resolve this, Im using the [[file:ha-programming.org::*Virtual Environments with direnv][direnv project]] abstraction for situations where I need project-specific isolation in more than just Python.
*** Virtual Environments
Use the built-in module, venv, to create isolated Python environments for specific projects, enabling you to manage dependencies separately.
Create a virtual environment, either in the projects directory, or in a global spot:
@ -160,8 +167,8 @@ Now, do what you need to do with this isolation:
#+BEGIN_SRC sh :tangle no
pip install -r test-requirements.txt
#+END_SRC
** Virtual Environment with new Python Version
Pyenv is a tool for managing multiple versions of Python on your machine, allowing you to switch between them easily. On a Mac, installed it via Homebrew:
*** Managing Python Versions
[[https://github.com/pyenv/pyenv][Pyenv]] is a tool for managing multiple versions of Python on your machine, allowing you to switch between them easily (see [[https://realpython.com/intro-to-pyenv/][this essay]]). On a Mac, installed it via Homebrew:
#+BEGIN_SRC sh
brew install readline xz
@ -213,16 +220,55 @@ Also, you need the following in your =~/.config/direnv/direnvrc= file (which I h
fi
}
#+end_src
** Editing Python Code
Lets integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project:
#+begin_src emacs-lisp
(when (fboundp 'evil-define-text-object)
(use-package evil-text-object-python
:hook (python-mode . evil-text-object-python-add-bindings)))
#+end_src
This allows me to delete a Python “block” using ~dal~.
** Docker Environment
Docker really allows you to isolate your project's environment. The downside is that you are using Docker and probably a bloated container. On my work laptop, a Mac, this creates a behemoth virtual machine that immediately spins the fans like a wind tunnel.
Tell Emacs about [[https://github.com/pythonic-emacs/pyenv-mode][pyenv-mode]]:
#+BEGIN_SRC emacs-lisp
(use-package pyenv-mode
:config
(defun setup-pyenv ()
"Pyenv."
(setenv "WORKON_HOME" "~/.pyenv/versions")
(pyenv-mode +1)))
#+END_SRC
Now specify the =pyenv= Python version by calling [[help:pyenv-mode-set][pyenv-mode-set]]:
#+begin_example
M-x pyenv-mode-set
#+end_example
When you run inferior Python processes (like =run-python=), the process will start inside the specified Python installation. You can unset the current version with:
#+begin_example
M-x pyenv-mode-unset
#+end_example
Or, we can do it automatically when we get into a project (if the project has a =.python-version= file):
#+BEGIN_SRC emacs-lisp
(use-package pyenv-mode
:config
(defun project-pyenv-mode-set (&rest _)
"Set pyenv version matching project name."
(let* ((filename (thread-first
(project-current)
(project-root)
(file-name-concat ".python-version")))
(version (when (file-exists-p filename)
(with-temp-buffer
(insert-file-contents filename)
(buffer-string)))))
(when version
(pyenv-mode-set version)
(pyenv-mode-unset))))
;; Either set/unset the pyenv version whenever changing tabs:
(add-hook 'tab-bar-tab-post-select-functions 'project-pyenv-mode-set))
#+END_SRC
*** Docker Environment
Docker allows you to isolate your project's environment. The downside is that you are using Docker and probably a bloated container. On my work laptop, a Mac, this creates a behemoth virtual machine that immediately spins the fans like a wind tunnel.
But, but... think of the dependencies!
@ -236,6 +282,14 @@ Your project's =.envrc= file would contain something like:
container_layout
#+end_src
** Editing Python Code
Lets integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project:
#+begin_src emacs-lisp
(when (fboundp 'evil-define-text-object)
(use-package evil-text-object-python
:hook (python-mode . evil-text-object-python-add-bindings)))
#+end_src
This allows me to delete a Python “block” using ~dal~.
** Unit Tests
#+begin_src emacs-lisp
(use-package python-pytest
@ -267,9 +321,22 @@ The [[https://elpy.readthedocs.io/en/latest/introduction.html][Elpy Project]] ex
#+BEGIN_SRC emacs-lisp
(use-package elpy
:ensure t
:init
(elpy-enable))
(advice-add 'python-mode :before 'elpy-enable)
:config
;; (elpy-enable)
(setq elpy-test-runner 'elpy-test-pytest-runner)
(setq elpy-formatter 'black)
(setq elpy-shell-echo-input nil)
(setq elpy-modules (delq 'elpy-module-flymake elpy-modules)))
#+END_SRC
After weve loaded the Company section, we can add jedi to the list of completions:
#+BEGIN_SRC emacs-lisp
(use-package elpy
:after company-mode
:config (add-to-list 'company-backends 'company-jedi))
#+END_SRC
Lets expand our =major-mode-hydra= with some extras:

View file

@ -670,26 +670,26 @@ Emacs has two LSP projects, and while I have used [[LSP Mode]], but since I don
:config
(global-set-key (kbd "s-m") 'lsp)
(ha-local-leader :keymaps 'prog-mode-map
"w" '(:ignore t :which-key "lsp")
"l" '(:ignore t :which-key "lsp")
"ws" '("start" . lsp))
;; (ha-local-leader :keymaps 'prog-mode-map
;; "w" '(:ignore t :which-key "lsp")
;; "l" '(:ignore t :which-key "lsp")
;; "ws" '("start" . lsp))
;; The following leader-like keys, are only available when I have
;; started LSP, and is an alternate to Command-m:
:general
(:states 'normal :keymaps 'lsp-mode-map
", w r" '("restart" . lsp-reconnect)
", w b" '("events" . lsp-events-buffer)
", w e" '("errors" . lsp-stderr-buffer)
", w q" '("quit" . lsp-shutdown)
", w Q" '("quit all" . lsp-shutdown-all)
;; ;; The following leader-like keys, are only available when I have
;; ;; started LSP, and is an alternate to Command-m:
;; :general
;; (:states 'normal :keymaps 'lsp-mode-map
;; ", w r" '("restart" . lsp-reconnect)
;; ", w b" '("events" . lsp-events-buffer)
;; ", w e" '("errors" . lsp-stderr-buffer)
;; ", w q" '("quit" . lsp-shutdown)
;; ", w Q" '("quit all" . lsp-shutdown-all)
", l r" '("rename" . lsp-rename)
", l f" '("format" . lsp-format)
", l a" '("actions" . lsp-code-actions)
", l i" '("imports" . lsp-code-action-organize-imports)
", l d" '("doc" . lsp-lookup-documentation))
;; ", l r" '("rename" . lsp-rename)
;; ", l f" '("format" . lsp-format)
;; ", l a" '("actions" . lsp-code-actions)
;; ", l i" '("imports" . lsp-code-action-organize-imports)
;; ", l d" '("doc" . lsp-lookup-documentation))
:hook ((lsp-mode . lsp-enable-which-key-integration)))
#+end_src

View file

@ -416,15 +416,25 @@ Favorite feature is the [[https://iterm2.com/documentation-status-bar.html][Stat
Currently, I show the currently defined Kube namespace.
#+BEGIN_SRC zsh
function iterm2_python_version() {
echo $(pyenv version-name):$(echo "$VIRTUAL_ENV" | sed "
s|^$HOME|~|
s|^~/src/wpc-gerrit.inday.io/||
s|^~/work/||
s|^~/.venv/||
s|/\.venv$||
s|\.venv$||")
}
function iterm2_print_user_vars() {
# iterm2_set_user_var kubecontext $($ yq '.users[0].name' ~/.kube/config):$(kubectl config view --minify --output 'jsonpath={..namespace}')
# iterm2_set_user_var kubecontext $($ yq '.users[0].name' ~/.kube/config):$(kubectl config view --minify --output 'jsonpath={..namespace}')
# Correct version:
# iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}')
# Faster version:
iterm2_set_user_var kubecontext $(awk '/^current-context:/{print $2;exit;}' <~/.kube/config)
# Correct version:
# iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}')
# Faster version:
iterm2_set_user_var kubecontext $(awk '/^current-context:/{print $2;exit;}' <~/.kube/config)
iterm2_set_user_var pycontext "$(pyenv version-name):$(echo $VIRTUAL_ENV | sed 's/.*.venv\///')"
iterm2_set_user_var pycontext $(iterm2_python_version)
}
#+END_SRC