lang/python: add support for more env managers

+ Rewritten +conda support
+ Adds +pyenv and +pyvenv flags with support.
+ New +ipython flag to enable ipython REPL support
+ Added pipenv support. This is the new default, instead of pyenv, and
  isn't hidden behind a module flag because it is officially endorsed by
  python.

Addresses #736
This commit is contained in:
Henrik Lissner 2018-07-31 14:02:34 +02:00
parent 85af18a04d
commit 560d16d651
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
5 changed files with 112 additions and 88 deletions

View file

@ -1,33 +0,0 @@
;;; lang/python/+conda.el -*- lexical-binding: t; -*-
;;;###if (featurep! +conda)
;; Adds conda support to Doom Emacs. `conda-anaconda-home' should be the path to
;; your anaconda installation, and will be guessed from the following:
;;
;; + ~/.anaconda3
;; + ~/.anaconda
;; + ~/usr/bin/anaconda3
;;
;; If none of these work, you'll need to set `conda-anaconda-home' yourself.
;;
;; Once set, run M-x `conda-env-activate' to switch between environments OR turn
;; on `conda-env-autoactivate-mode' if you want it done automatically.
(def-package! conda
:when (featurep! +conda)
:after python
:config
(unless (cl-loop for dir in (list conda-anaconda-home "/usr/bin/anaconda3" "~/.anaconda")
if (file-directory-p dir)
return (setq conda-anaconda-home dir
conda-env-home-directory dir))
(message "Cannot find Anaconda installation"))
;; integration with term/eshell
(conda-env-initialize-interactive-shells)
(after! eshell (conda-env-initialize-eshell))
(add-hook! '(conda-postactivate-hook conda-postdeactivate-hook)
#'+python|add-conda-env-to-modeline)
(advice-add 'anaconda-mode-bootstrap :override #'+python*anaconda-mode-bootstrap-in-remote-environments))

View file

@ -16,12 +16,10 @@ executable and packages."
(message "Successfully changed conda home to: %s" (abbreviate-file-name home)))) (message "Successfully changed conda home to: %s" (abbreviate-file-name home))))
;;;###autoload ;;;###autoload
(defun +python|add-conda-env-to-modeline () (defun +python-conda-env ()
"Add conda environment string to the major mode modeline segment." "Add conda environment string to the major mode modeline segment."
(setq mode-name (when conda-env-current-name
(if conda-env-current-name (format "conda:%s" conda-env-current-name)))
(format "Py:conda:%s" conda-env-current-name)
"Python")))
;;;###autoload ;;;###autoload
(defun +python*anaconda-mode-bootstrap-in-remote-environments (&optional callback) (defun +python*anaconda-mode-bootstrap-in-remote-environments (&optional callback)

View file

@ -5,3 +5,25 @@
"Open the Python REPL." "Open the Python REPL."
(interactive) (interactive)
(process-buffer (run-python nil t t))) (process-buffer (run-python nil t t)))
;;;###autoload
(defun +python-version ()
"Return the currently installed version of python on your system or active in
the current pipenv.
This is not necessarily aware of env management tools like virtualenv, pyenv or
pipenv, unless those tools have modified the PATH that Emacs picked up when you
started it."
(let* ((command (if (pipenv-project-p)
"pipenv run python --version"
"python --version"))
(bin (car (split-string command " "))))
(unless (executable-find bin)
(user-error "Couldn't find %s executable in PATH" bin))
(with-temp-buffer
(let ((p (apply #'call-process bin nil (current-buffer) nil
(cdr (split-string command " " t))))
(output (string-trim (buffer-string))))
(unless (zerop p)
(user-error "'%s' failed: %s" command output))
(nth 1 (split-string output " " t))))))

View file

@ -1,14 +1,9 @@
;;; lang/python/config.el -*- lexical-binding: t; -*- ;;; lang/python/config.el -*- lexical-binding: t; -*-
(defvar +python-pyenv-root nil (defvar +python-mode-name-functions '(+python-version)
"The path to pyenv's root directory. This is automatically set when `python' "A list of functions to retrieve a version or environment string from. The
is loaded.") first to return non-nil will have its result appended to the python-mode
`mode-name' and displayed in the mode-line.")
(defvar +python-pyenv-versions nil
"Available versions of python in pyenv.")
(defvar-local +python-current-version nil
"The currently active pyenv version.")
;; ;;
@ -22,7 +17,7 @@ is loaded.")
python-indent-guess-indent-offset-verbose nil python-indent-guess-indent-offset-verbose nil
python-shell-interpreter "python") python-shell-interpreter "python")
:config :config
(set-env! "PYTHONPATH" "PYENV_ROOT") (set-env! "PYTHONPATH" "PYENV")
(set-electric! 'python-mode :chars '(?:)) (set-electric! 'python-mode :chars '(?:))
(set-repl-handler! 'python-mode #'+python/repl) (set-repl-handler! 'python-mode #'+python/repl)
@ -44,7 +39,11 @@ is loaded.")
:for "for" :for "for"
:return "return" :yield "yield") :return "return" :yield "yield")
(when (executable-find "ipython") (define-key python-mode-map (kbd "DEL") nil) ; interferes with smartparens
(sp-with-modes 'python-mode
(sp-local-pair "'" nil :unless '(sp-point-before-word-p sp-point-after-word-p sp-point-before-same-p)))
(when (featurep! +ipython)
(setq python-shell-interpreter "ipython" (setq python-shell-interpreter "ipython"
python-shell-interpreter-args "-i --simple-prompt --no-color-info" python-shell-interpreter-args "-i --simple-prompt --no-color-info"
python-shell-prompt-regexp "In \\[[0-9]+\\]: " python-shell-prompt-regexp "In \\[[0-9]+\\]: "
@ -55,45 +54,21 @@ is loaded.")
python-shell-completion-string-code python-shell-completion-string-code
"';'.join(get_ipython().Completer.all_completions('''%s'''))\n")) "';'.join(get_ipython().Completer.all_completions('''%s'''))\n"))
;; Version management with pyenv
(defun +python|add-version-to-modeline () (defun +python|add-version-to-modeline ()
"Add version string to the major mode in the modeline." "Add version string to the major mode in the modeline."
(setq mode-name (setq mode-name
(if +python-current-version (if-let* ((result (run-hook-with-args-until-success '+python-mode-name-functions)))
(format "Python %s" +python-current-version) (format "Python %s" result)
"Python"))) "Python")))
(add-hook 'python-mode-hook #'+python|add-version-to-modeline) (add-hook 'python-mode-hook #'+python|add-version-to-modeline))
(if (not (executable-find "pyenv"))
(setq-default +python-current-version (string-trim (shell-command-to-string "python --version 2>&1 | cut -d' ' -f2")))
(setq +python-pyenv-root (string-trim (shell-command-to-string "pyenv root"))
+python-pyenv-versions (split-string (shell-command-to-string "pyenv versions --bare") "\n" t))
(defun +python|detect-pyenv-version ()
"Detect the pyenv version for the current project and set the relevant
environment variables."
(when-let* ((version-str (shell-command-to-string "PYENV_VERSION= python --version 2>&1 | cut -d' ' -f2")))
(setq version-str (string-trim version-str)
+python-current-version version-str)
(let ((pyenv-current-path (concat +python-pyenv-root "/versions/" version-str)))
(when (file-directory-p pyenv-current-path)
(setq pythonic-environment pyenv-current-path)))
(when (member version-str +python-pyenv-versions)
(setenv "PYENV_VERSION" version-str))))
(add-hook 'python-mode-hook #'+python|detect-pyenv-version))
(define-key python-mode-map (kbd "DEL") nil) ; interferes with smartparens
(sp-with-modes 'python-mode
(sp-local-pair "'" nil :unless '(sp-point-before-word-p sp-point-after-word-p sp-point-before-same-p))))
(def-package! anaconda-mode (def-package! anaconda-mode
:after python :hook python-mode
:init :init
(setq anaconda-mode-installation-directory (concat doom-etc-dir "anaconda/") (setq anaconda-mode-installation-directory (concat doom-etc-dir "anaconda/")
anaconda-mode-eldoc-as-single-line t) anaconda-mode-eldoc-as-single-line t)
:config :config
(add-hook 'python-mode-hook #'anaconda-mode)
(add-hook 'anaconda-mode-hook #'anaconda-eldoc-mode) (add-hook 'anaconda-mode-hook #'anaconda-eldoc-mode)
(set-company-backend! 'python-mode '(company-anaconda)) (set-company-backend! 'python-mode '(company-anaconda))
(set-popup-rule! "^\\*anaconda-mode" :select nil) (set-popup-rule! "^\\*anaconda-mode" :select nil)
@ -111,6 +86,8 @@ environment variables."
(add-hook! 'python-mode-hook (add-hook! 'python-mode-hook
(add-hook 'kill-buffer-hook #'+python|auto-kill-anaconda-processes nil t)) (add-hook 'kill-buffer-hook #'+python|auto-kill-anaconda-processes nil t))
(when (featurep 'evil)
(add-hook 'anaconda-mode-hook #'evil-normalize-keymaps))
(map! :map anaconda-mode-map (map! :map anaconda-mode-map
:localleader :localleader
:prefix "f" :prefix "f"
@ -123,13 +100,14 @@ environment variables."
(def-package! nose (def-package! nose
:commands nose-mode :commands nose-mode
:preface :preface (defvar nose-mode-map (make-sparse-keymap))
(defvar nose-mode-map (make-sparse-keymap)) :init (associate! nose-mode :match "/test_.+\\.py$" :modes (python-mode))
:init
(associate! nose-mode :match "/test_.+\\.py$" :modes (python-mode))
:config :config
(set-popup-rule! "^\\*nosetests" :size 0.4 :select nil) (set-popup-rule! "^\\*nosetests" :size 0.4 :select nil)
(set-yas-minor-mode! 'nose-mode) (set-yas-minor-mode! 'nose-mode)
(when (featurep 'evil)
(add-hook 'nose-mode-hook #'evil-normalize-keymaps))
(map! :map nose-mode-map (map! :map nose-mode-map
:localleader :localleader
:prefix "t" :prefix "t"
@ -143,9 +121,59 @@ environment variables."
;; ;;
;; Evil integration ;; Environment management
;; ;;
(when (featurep! :feature evil +everywhere) (def-package! pipenv
(add-hook! '(anaconda-mode-hook nose-mode-hook) :commands pipenv-project-p
#'evil-normalize-keymaps)) :hook (python-mode . pipenv-mode))
(def-package! pyenv-mode
:when (featurep! +pyenv)
:after python
:config
(pyenv-mode +1)
(add-to-list '+python-mode-name-functions #'pyenv-mode-version nil #'eq))
(def-package! pyvenv
:when (featurep! +pyvenv)
:after python
:config
(defun +python-current-pyvenv () pyvenv-virtual-env-name)
(add-to-list '+python-mode-name-functions #'+python-current-pyvenv nil #'eq))
(def-package! conda
:when (featurep! +conda)
:after python
:config
;; Adds conda support to Doom Emacs. `conda-anaconda-home' should be the path
;; to your anaconda installation, and will be guessed from the following:
;;
;; + ~/.anaconda3
;; + ~/.anaconda
;; + ~/.miniconda
;; + ~/usr/bin/anaconda3
;;
;; If none of these work, you'll need to set `conda-anaconda-home' yourself.
;;
;; Once set, run M-x `conda-env-activate' to switch between environments OR
;; turn on `conda-env-autoactivate-mode' if you want it done automatically.
(unless (cl-loop for dir in (list conda-anaconda-home
"~/.anaconda"
"~/.miniconda"
"/usr/bin/anaconda3")
if (file-directory-p dir)
return (setq conda-anaconda-home dir
conda-env-home-directory dir))
(message "Cannot find Anaconda installation"))
;; integration with term/eshell
(conda-env-initialize-interactive-shells)
(after! eshell (conda-env-initialize-eshell))
(add-to-list '+python-mode-name-functions #'+python-conda-env nil #'eq)
(advice-add 'anaconda-mode-bootstrap :override #'+python*anaconda-mode-bootstrap-in-remote-environments))

View file

@ -1,12 +1,21 @@
;; -*- no-byte-compile: t; -*- ;; -*- no-byte-compile: t; -*-
;;; lang/python/packages.el ;;; lang/python/packages.el
;; requires: python jedi setuptools ;; requires: python setuptools
(package! nose) (package! nose)
(package! pip-requirements) (package! pip-requirements)
;; Environmet management
(package! pipenv)
(when (featurep! +pyenv)
(package! pyenv-mode))
(when (featurep! +pyvenv)
(package! pyvenv-mode))
(when (featurep! +conda)
(package! conda))
;; Programming environment
(when (package! anaconda-mode) (when (package! anaconda-mode)
(when (featurep! :completion company) (when (featurep! :completion company)
(package! company-anaconda))) (package! company-anaconda)))
(when (featurep! +conda)
(package! conda))