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'] # c.InteractiveShellApp.exec_lines = ['%autoreload 2']
#+END_SRC #+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. 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: 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 #+BEGIN_SRC sh :tangle no
pip install -r test-requirements.txt pip install -r test-requirements.txt
#+END_SRC #+END_SRC
** Virtual Environment with new Python Version *** Managing Python Versions
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: [[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 #+BEGIN_SRC sh
brew install readline xz brew install readline xz
@ -213,16 +220,55 @@ Also, you need the following in your =~/.config/direnv/direnvrc= file (which I h
fi fi
} }
#+end_src #+end_src
** Editing Python Code
Lets integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project: Tell Emacs about [[https://github.com/pythonic-emacs/pyenv-mode][pyenv-mode]]:
#+begin_src emacs-lisp
(when (fboundp 'evil-define-text-object) #+BEGIN_SRC emacs-lisp
(use-package evil-text-object-python (use-package pyenv-mode
:hook (python-mode . evil-text-object-python-add-bindings))) :config
#+end_src (defun setup-pyenv ()
This allows me to delete a Python “block” using ~dal~. "Pyenv."
** Docker Environment (setenv "WORKON_HOME" "~/.pyenv/versions")
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. (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! But, but... think of the dependencies!
@ -236,6 +282,14 @@ Your project's =.envrc= file would contain something like:
container_layout container_layout
#+end_src #+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 ** Unit Tests
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package python-pytest (use-package python-pytest
@ -267,9 +321,22 @@ The [[https://elpy.readthedocs.io/en/latest/introduction.html][Elpy Project]] ex
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package elpy (use-package elpy
:ensure t
:init :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 #+END_SRC
Lets expand our =major-mode-hydra= with some extras: 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 :config
(global-set-key (kbd "s-m") 'lsp) (global-set-key (kbd "s-m") 'lsp)
(ha-local-leader :keymaps 'prog-mode-map ;; (ha-local-leader :keymaps 'prog-mode-map
"w" '(:ignore t :which-key "lsp") ;; "w" '(:ignore t :which-key "lsp")
"l" '(:ignore t :which-key "lsp") ;; "l" '(:ignore t :which-key "lsp")
"ws" '("start" . lsp)) ;; "ws" '("start" . lsp))
;; The following leader-like keys, are only available when I have ;; ;; The following leader-like keys, are only available when I have
;; started LSP, and is an alternate to Command-m: ;; ;; started LSP, and is an alternate to Command-m:
:general ;; :general
(:states 'normal :keymaps 'lsp-mode-map ;; (:states 'normal :keymaps 'lsp-mode-map
", w r" '("restart" . lsp-reconnect) ;; ", w r" '("restart" . lsp-reconnect)
", w b" '("events" . lsp-events-buffer) ;; ", w b" '("events" . lsp-events-buffer)
", w e" '("errors" . lsp-stderr-buffer) ;; ", w e" '("errors" . lsp-stderr-buffer)
", w q" '("quit" . lsp-shutdown) ;; ", w q" '("quit" . lsp-shutdown)
", w Q" '("quit all" . lsp-shutdown-all) ;; ", w Q" '("quit all" . lsp-shutdown-all)
", l r" '("rename" . lsp-rename) ;; ", l r" '("rename" . lsp-rename)
", l f" '("format" . lsp-format) ;; ", l f" '("format" . lsp-format)
", l a" '("actions" . lsp-code-actions) ;; ", l a" '("actions" . lsp-code-actions)
", l i" '("imports" . lsp-code-action-organize-imports) ;; ", l i" '("imports" . lsp-code-action-organize-imports)
", l d" '("doc" . lsp-lookup-documentation)) ;; ", l d" '("doc" . lsp-lookup-documentation))
:hook ((lsp-mode . lsp-enable-which-key-integration))) :hook ((lsp-mode . lsp-enable-which-key-integration)))
#+end_src #+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. Currently, I show the currently defined Kube namespace.
#+BEGIN_SRC zsh #+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() { 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: # Correct version:
# iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}') # iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}')
# Faster version: # Faster version:
iterm2_set_user_var kubecontext $(awk '/^current-context:/{print $2;exit;}' <~/.kube/config) 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 #+END_SRC