2023-12-03 18:57:36 +00:00
#+title : Configuring Python in Emacs
#+author : Howard X. Abrams
#+date : 2021-11-16
#+tags : emacs python programming
2021-11-18 16:17:20 +00:00
A literate programming file for configuring Python.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; ha-programming-python --- Python configuration. -*- lexical-binding: t; -* -
2021-11-18 16:17:20 +00:00
;;
2023-02-23 17:35:36 +00:00
;; © 2021-2023 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; See http://creativecommons.org/licenses/by/4.0/
2021-11-18 16:17:20 +00:00
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
2021-11-18 20:12:19 +00:00
;; Maintainer: Howard X. Abrams
2021-11-18 16:17:20 +00:00
;; Created: November 16, 2021
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
2021-12-27 17:34:03 +00:00
;; ~/other/hamacs/ha-programming-python.org
2021-11-18 16:17:20 +00:00
;; And tangle the file to recreate this one.
;;
;;; Code:
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
* Introduction
2022-06-18 00:25:47 +00:00
The critical part of Python integration with Emacs is running LSP in Python using [[file:ha-programming.org::*direnv ][direnv ]]. And the question to ask is if the Python we run it in Docker or in a virtual environment.
2022-02-26 01:12:45 +00:00
While Emacs supplies a Python editing environment, we’ ll still use =use-package= to grab the latest:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-26 01:12:45 +00:00
(use-package python
2024-02-09 22:00:12 +00:00
:after flycheck
2022-02-26 01:12:45 +00:00
:mode ("[./]flake8\\'" . conf-mode)
:mode ("/Pipfile\\'" . conf-mode)
:init
2022-12-03 01:06:31 +00:00
(setq python-indent-guess-indent-offset-verbose nil
flycheck-flake8-maximum-line-length 120)
2022-02-26 01:12:45 +00:00
:config
(when (and (executable-find "python3")
2022-05-10 18:25:10 +00:00
(string= python-shell-interpreter "python"))
2022-02-26 01:12:45 +00:00
(setq python-shell-interpreter "python3"))
2022-12-03 01:06:31 +00:00
(flycheck-add-next-checker 'python-pylint 'python-pycompile 'append))
#+end_src
Note: Install the following checks:
#+begin_src sh
pip install flake8 pylint pyright mypy pycompile
2022-06-18 00:25:47 +00:00
#+end_src
2022-12-03 01:06:31 +00:00
Or better yet, add those to the =requirements-dev.txt= file.
2021-11-18 16:17:20 +00:00
** Virtual Environment
2022-12-03 01:06:31 +00:00
For a local virtual machine, put the following in your =.envrc= file:
2021-11-18 16:17:20 +00:00
#+begin_src conf
layout_python3
#+end_src
That is pretty slick and simple.
The old way, that we still use if you need a particular version of Python, is to install [[https://github.com/pyenv/pyenv ][pyenv ]] globally:
2022-06-18 00:25:47 +00:00
#+begin_src sh
2021-11-18 16:17:20 +00:00
pip install pyenv
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
And have this in your =.envrc= file:
#+begin_src conf
use python 3.7.1
#+end_src
Also, you need the following in your =~/.config/direnv/direnvrc= file (which I have):
#+begin_src shell
use_python() {
local python_root=$(pyenv root)/versions/ $1
load_prefix "$python_root"
if [[ -x "$python_root/bin/python" ]]; then
layout python "$python_root/bin/python"
else
echo "Error: $python_root/bin/python can't be executed."
exit
fi
}
#+end_src
2022-04-28 05:09:03 +00:00
** Editing Python Code
Let’ s integrate this [[https://github.com/wbolster/evil-text-object-python ][Python support for evil-text-object ]] project:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2024-02-09 20:05:13 +00:00
(when (fboundp 'evil-define-text-object)
(use-package evil-text-object-python
:hook (python-mode . evil-text-object-python-add-bindings)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 05:09:03 +00:00
This allows me to delete a Python “block” using ~dal~ .
2021-11-18 16:17:20 +00:00
** 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.
But, but... think of the dependencies!
Enough of the rant (I go back and forth), after getting Docker installed and running (ooo Podman ... shiny), and you've created a =Dockerfile= for your project, let's install [[https://github.com/snbuback/container-env ][container-env ]].
Your project's =.envrc= file would contain something like:
#+begin_src shell
CONTAINER_NAME=my-docker-container
CONTAINER_WRAPPERS=(python3 pip3 yamllint)
CONTAINER_EXTRA_ARGS="--env SOME_ENV_VAR= ${SOME_ENV_VAR}"
container_layout
#+end_src
2022-02-26 01:12:45 +00:00
** Unit Tests
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-26 01:12:45 +00:00
(use-package python-pytest
:after python
:commands python-pytest-dispatch
:init
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'python-mode-map
2022-02-26 01:12:45 +00:00
"t" '(:ignore t :which-key "tests")
"t a" '("all" . python-pytest)
"t f" '("file dwim" . python-pytest-file-dwim)
"t F" '("file" . python-pytest-file)
"t t" '("function-dwim" . python-pytest-function-dwim)
"t T" '("function" . python-pytest-function)
"t r" '("repeat" . python-pytest-repeat)
"t p" '("dispatch" . python-pytest-dispatch)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
** Python Dependencies
2022-02-26 01:12:45 +00:00
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/ ][python-lsp-server ]] (not the /unmaintained/ project, =python-language-server= ):
#+begin_src conf :tangle no
2021-11-18 16:17:20 +00:00
python-lsp-server[all]
#+end_src
*Note:* This does mean, you would have a =tox.ini= with this line:
2022-06-18 00:25:47 +00:00
#+begin_src conf
2021-11-18 16:17:20 +00:00
[tox]
minversion = 1.6
skipsdist = True
envlist = linters
ignore_basepython_conflict = True
[testenv]
basepython = python3
install_command = pip install {opts} {packages}
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
stestr slowest
# ...
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-26 01:12:45 +00:00
*** Pyright
I’ m using the Microsoft-supported [[https://github.com/Microsoft/pyright ][pyright ]] package instead. Adding this to my =requirements.txt= files:
#+begin_src conf :tangle no
pyright
#+end_src
The [[https://github.com/emacs-lsp/lsp-pyright ][pyright package ]] works with LSP.
2022-08-10 04:29:41 +00:00
#+begin_src emacs-lisp :tangle no
2022-02-26 01:12:45 +00:00
(use-package lsp-pyright
:hook (python-mode . (lambda () (require 'lsp-pyright)))
:init (when (executable-find "python3")
(setq lsp-pyright-python-executable-cmd "python3")))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
* LSP Integration of Python
2023-03-20 21:01:40 +00:00
Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integration ][LSP Integration ]] is complete, we can stitch the two projects together, by calling =lsp= . I oscillate between automatically turning on LSP mode with every Python file, but I sometimes run into issues when starting, so I turn it on with ~, w s~ .
2021-11-18 16:17:20 +00:00
2022-08-10 04:29:41 +00:00
#+begin_src emacs-lisp :tangle no
2021-11-18 16:17:20 +00:00
(use-package lsp-mode
2022-05-10 18:25:10 +00:00
;; :hook ((python-mode . lsp)))
:config
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'lsp-mode-map
2022-05-10 18:25:10 +00:00
"0" '("treemacs" . lsp-treemacs-symbols)
"/" '("complete" . completion-at-point)
"k" '("check code" . python-check)
"]" '("shift left" . python-indent-shift-left)
"[" '("shift right" . python-indent-shift-right)
;; actions
"a" '(:ignore t :which-key "code actions")
"aa" '("code actions" . lsp-execute-code-action)
"ah" '("highlight symbol" . lsp-document-highlight)
"al" '("lens" . lsp-avy-lens)
;; formatting
"=" '(:ignore t :which-key "formatting")
"==" '("format buffer" . lsp-format-buffer)
"=r" '("format region" . lsp-format-region)
"e" '(:ignore t :which-key "eval")
"e P" '("run python" . run-python)
"e e" '("send statement" . python-shell-send-statement)
"e b" '("send buffer" . python-shell-send-buffer)
"e f" '("send defun" . python-shell-send-defun)
"e F" '("send file" . python-shell-send-file)
"e r" '("send region" . python-shell-send-region)
"e ;" '("expression" . python-shell-send-string)
"e p" '("switch-to-shell" . python-shell-switch-to-shell)
;; folders
"F" '(:ignore t :which-key "folders")
"Fa" '("add folder" . lsp-workspace-folders-add)
"Fb" '("un-blacklist folder" . lsp-workspace-blacklist-remove)
"Fr" '("remove folder" . lsp-workspace-folders-remove)
;; goto
"g" '(:ignore t :which-key "goto")
"ga" '("find symbol in workspace" . xref-find-apropos)
"gd" '("find declarations" . lsp-find-declaration)
"ge" '("show errors" . lsp-treemacs-errors-list)
"gg" '("find definitions" . lsp-find-definition)
"gh" '("call hierarchy" . lsp-treemacs-call-hierarchy)
"gi" '("find implementations" . lsp-find-implementation)
"gm" '("imenu" . lsp-ui-imenu)
"gr" '("find references" . lsp-find-references)
"gt" '("find type definition" . lsp-find-type-definition)
;; peeks
"G" '(:ignore t :which-key "peek")
"Gg" '("peek definitions" . lsp-ui-peek-find-definitions)
"Gi" '("peek implementations" . lsp-ui-peek-find-implementation)
"Gr" '("peek references" . lsp-ui-peek-find-references)
"Gs" '("peek workspace symbol" . lsp-ui-peek-find-workspace-symbol)
;; help
"h" '(:ignore t :which-key "help")
"he" '("eldoc" . python-eldoc-at-point)
"hg" '("glance symbol" . lsp-ui-doc-glance)
"hh" '("describe symbol at point" . lsp-describe-thing-at-point)
"gH" '("describe python symbol" . python-describe-at-point)
"hs" '("signature help" . lsp-signature-activate)
"i" 'imenu
;; refactoring
"r" '(:ignore t :which-key "refactor")
"ro" '("organize imports" . lsp-organize-imports)
"rr" '("rename" . lsp-rename)
;; toggles
"t" '(:ignore t :which-key "toggle")
"tD" '("toggle modeline diagnostics" . lsp-modeline-diagnostics-mode)
"tL" '("toggle log io" . lsp-toggle-trace-io)
"tS" '("toggle sideline" . lsp-ui-sideline-mode)
"tT" '("toggle treemacs integration" . lsp-treemacs-sync-mode)
"ta" '("toggle modeline code actions" . lsp-modeline-code-actions-mode)
"tb" '("toggle breadcrumb" . lsp-headerline-breadcrumb-mode)
"td" '("toggle documentation popup" . lsp-ui-doc-mode)
"tf" '("toggle on type formatting" . lsp-toggle-on-type-formatting)
"th" '("toggle highlighting" . lsp-toggle-symbol-highlight)
"tl" '("toggle lenses" . lsp-lens-mode)
"ts" '("toggle signature" . lsp-toggle-signature-auto-activate)
;; workspaces
"w" '(:ignore t :which-key "workspaces")
"wD" '("disconnect" . lsp-disconnect)
"wd" '("describe session" . lsp-describe-session)
"wq" '("shutdown server" . lsp-workspace-shutdown)
"wr" '("restart server" . lsp-workspace-restart)
"ws" '("start server" . lsp)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-26 01:12:45 +00:00
* Project Configuration
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/ :
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-26 01:12:45 +00:00
(defun ha-python-configure-project (proj-directory)
"Configure PROJ-DIRECTORY for LSP and Python."
(interactive "DPython Project: ")
(let ((default-directory proj-directory))
(unless (f-exists? ".envrc")
(message "Configuring direnv")
(with-temp-file ".envrc"
;; (insert "use_python 3.7.4\n")
(insert "layout_python3\n"))
(direnv-allow))
(unless (f-exists? ".pip.conf")
(message "Configuring pip")
(with-temp-file ".pip.conf"
(insert "[global]\n")
(insert "index-url = https://pypi.python.org/simple\n"))
(shell-command "pipconf --local")
(shell-command "pip install --upgrade pip"))
(message "Configuring pip for LSP")
(with-temp-file "requirements-dev.txt"
(insert "python-lsp-server[all]\n")
;; Let's install these extra packages individually ...
(insert "pyls-flake8\n")
;; (insert "pylsp-mypy")
;; (insert "pyls-isort")
;; (insert "python-lsp-black")
;; (insert "pyls-memestra")
(insert "pylsp-rope\n"))
2024-02-09 22:00:12 +00:00
(shell-command "pip install -r requirements-dev.txt")))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2021-11-18 16:17:20 +00:00
(provide 'ha-programming-python)
;;; ha-programming-python.el ends here
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-18 16:17:20 +00:00
#+DESCRIPTION : A literate programming file for configuring Python.
#+PROPERTY : header-args:sh :tangle no
2023-07-05 20:20:55 +00:00
#+PROPERTY : header-args:emacs-lisp :tangle yes
2021-11-18 16:17:20 +00:00
#+PROPERTY : header-args :results none :eval no-export :comments no mkdirp yes
2023-12-21 03:54:30 +00:00
#+OPTIONS : num:nil toc:t todo:nil tasks:nil tags:nil date:nil
2021-11-18 16:17:20 +00:00
#+OPTIONS : skip:nil author:nil email:nil creator:nil timestamp:nil
2023-12-21 03:54:30 +00:00
#+INFOJS_OPT : view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js