Add a "Package Installer for Python" section

This commit is contained in:
Gerard Vermeulen 2023-02-26 14:04:44 +01:00
parent 97cc936aee
commit 402f89d7fe
1 changed files with 226 additions and 0 deletions

View File

@ -4872,6 +4872,232 @@ ruff "$@" | cat
# End:
#+end_src
*** [[https://pip.pypa.io/en/stable/][Package Installer for Python]]
:PROPERTIES:
:CUSTOM_ID: sec:pip-python
:header-args: :eval never-export
:END:
#+caption[Show how to use =pip list --outdated=]:
#+caption: Show how to use =pip list --outdated=.
#+header: :wrap src text
#+name: lst:shell-pip-list-outdated
#+begin_src shell :exports both :results verbatim
# WARNING: "pip index" is currently an experimental command.
pip list --outdated
#+end_src
#+caption[The result of =pip list --outdated=]:
#+caption: The result of =pip list --outdated=.
#+RESULTS: lst:shell-pip-list-outdated
#+begin_src text
Package Version Latest Type
------------------- ------- ------ -----
aiofiles 22.1.0 23.1.0 wheel
jupyter_server_ydoc 0.6.1 0.7.0 wheel
jupyter-ydoc 0.2.2 0.3.4 wheel
y-py 0.5.9 0.6.0 wheel
ypy-websocket 0.8.2 0.8.4 wheel
#+end_src
#+caption[Show how to use =pip index versions=]:
#+caption: Show how to use =pip index versions=.
#+header: :wrap src text
#+name: lst:shell-pip-index-versions
#+begin_src shell :exports both :results verbatim
# WARNING: "pip index" is currently an experimental command.
pip index versions circular-buffer --no-color
#+end_src
#+caption[The result of =pip index versions=]:
#+caption: The result of =pip index versions=.
#+RESULTS: lst:shell-pip-index-versions
#+begin_src text
circular-buffer (0.2.0)
Available versions: 0.2.0, 0.1.1, 0.1.0
#+end_src
#+caption[Emacs interface to list outdated Python packages]:
#+caption: Emacs interface to list outdated Python packages.
#+name: lst:pip-list-outdated
#+begin_src emacs-lisp -n results silent
(defvar pip-outdated-packages nil
"Outdated Python packages.")
(defun pip--list-outdated-sentinel (process event)
(when (and (eq (process-status process) 'exit)
(zerop (process-exit-status process))
(buffer-live-p (process-buffer process)))
(with-current-buffer (process-buffer process)
(goto-char (point-min))
(setq pip-outdated-packages
(json-parse-buffer :array-type 'list :object-type 'alist))
(let ((alists pip-outdated-packages))
(while alists
(setf (alist-get 'name (car alists))
(string-replace "_" "-" (alist-get 'name (car alists))))
(setq alists (cdr alists))))
(kill-buffer)
(message "Calling `%S' succeeded" #'pip-list-outdated))))
(defun pip-list-outdated ()
"Save the outdated Python packages in `pip-outdated-packages'
This invokes an asynchonous process and finishes with a message."
(interactive)
(let ((command '("pip" "list" "--outdated" "--format" "json")))
(make-process
:name "pip-list-outdated"
:buffer (generate-new-buffer-name "*pip-list-outdated-output*")
:command command
:sentinel #'pip--list-outdated-sentinel)
(message "Running `%s' asynchonously" (string-join command " "))))
#+end_src
#+caption[Emacs interface to upgrade outdated unfrozen Python packages]:
#+caption: Emacs interface to upgrade outdated unfrozen Python packages.
#+name: lst:pip-upgrade-maybe
#+begin_src emacs-lisp -n :results silent
(defcustom pip-frozen-packages '("aiofiles"
"jupyter-server-ydoc"
"jupyter-ydoc"
"y-py"
"ypy-websocket")
"Frozen Python packages (until jupyterlab-4.0.0)"
:type '(repeat string))
(defun pip--upgrade-maybe-sentinel (process event)
(when (and (eq (process-status process) 'exit)
(buffer-live-p (process-buffer process)))
(display-buffer (process-buffer process))))
(defun pip-upgrade-maybe ()
"Install the latest version of outdated packages unless they are frozen.
This invokes an asynchonous process and finishes with displaying the process
buffer to check whether upgrading has made the dependencies incompatible."
(interactive)
(let (found)
(dolist (alist pip-outdated-packages found)
(let ((name (alist-get 'name alist))
(latest_version (alist-get 'latest_version alist)))
(unless (member name pip-frozen-packages)
(push (format "%s==%s" name latest_version) found))))
(if (consp found)
(let ((command
`("pip" "install" "--progress-bar" "off" ,@(nreverse found))))
(make-process
:name "pip-upgrade-maybe"
:buffer (generate-new-buffer-name "*pip-upgrade-maybe*")
:command command
:sentinel #'pip--upgrade-maybe-sentinel)
(message "Running `%s' asynchonously" (string-join command " ")))
(message "`pip-upgrade-maybe' found no packages to install"))))
#+end_src
#+caption[Emacs interface to the =PyPI= simple =JSON= =API=]:
#+caption: Emacs interface to the =PyPI= simple =JSON= =API=.
#+name: lst:pip-pypi-simple-json-apip
#+begin_src emacs-lisp -n :results silent
(defun pip-simple-json-project-details (name)
(let ((url (format "https://pypi.org/simple/%s" name))
(url-request-method "GET")
(url-mime-accept-string "application/vnd.pypi.simple.latest+json"))
(url-retrieve url
(lambda (status) (switch-to-buffer (current-buffer))))))
(defun pip-simple-json-project-list ()
(interactive)
(let ((url-request-method "GET")
(url-mime-accept-string "application/vnd.pypi.simple.latest+json"))
(url-retrieve "https://pypi.org/simple"
(lambda (status) (switch-to-buffer (current-buffer))))))
#+end_src
#+caption[Emacs interface to get the dependency tree of installed packages]:
#+caption: Emacs interface to get the dependency tree of installed packages.
#+name: lst:pip-dependency-tree
#+begin_src emacs-lisp :results silent
(defvar pip-package-dependency-tree nil
"Python package dependency tree.")
(defun pip--dependency-tree-sentinel (process event)
(when (and (eq (process-status process) 'exit)
(zerop (process-exit-status process))
(buffer-live-p (process-buffer process)))
(with-current-buffer (process-buffer process)
(goto-char (point-min))
(setq pip-package-dependency-tree
(json-parse-buffer :array-type 'list :object-type 'alist))
(kill-buffer)
(message "Calling `%S' succeeded" #'pip-dependency-tree))))
(defun pip-dependency-tree ()
"Save the Python package dependency tree in `pip-package-dependency-tree'."
(interactive)
(let ((command '("pipdeptree" "--json-tree")))
(make-process
:name "pip-dependency-tree"
:buffer (generate-new-buffer-name "*pip-dependency-tree-output*")
:command command
:sentinel #'pip--dependency-tree-sentinel)
(message "Running `%s' asynchonously" (string-join command " "))))
#+end_src
#+caption[Emacs functions to flatten the Python package dependency tree]:
#+caption: Emacs functions to flatten the Python package dependency tree.
#+name: lst:pip-python-interface
#+begin_src emacs-lisp -n :results silent
(defun pip--flatten-dependencies (parents found)
(dolist (parent parents)
(when-let ((children (alist-get 'dependencies parent)))
(setq found (pip--flatten-dependencies children found)))
(if-let* ((pair `(key . ,(alist-get 'key parent)))
(old (cl-loop for alist in found thereis (member pair alist)))
(rvs (split-string (alist-get 'required_version parent) "[,]+")))
(dolist (rv rvs)
(cl-pushnew rv (alist-get 'required_version old) :test #'equal))
(let* ((new (assq-delete-all 'dependencies (copy-alist parent)))
(rvs (split-string (alist-get 'required_version new) "[,]+")))
(setf (alist-get 'required_version new) rvs)
(push new found))))
found)
(defun pip-flatten-package-dependency-tree ()
(let (found)
(dolist (package pip-package-dependency-tree)
(when-let ((children (alist-get 'dependencies package)))
(setq found (pip--flatten-dependencies children found))))
found))
(defconst pip-canonical-version-regexp-alist
'(("^[.]dev$" . -4)
("^a$" . -3)
("^b$" . -2)
("^rc$" . -1)
("^[.]post$" . 1))
"Specify association between canonical \"PEP-440\" version and its priority.")
(defun pip-canonical-version-to-list (version)
(let ((version-regexp-alist pip-canonical-version-regexp-alist))
(version-to-list version)))
;; https://peps.python.org/pep-0440/
(defun pip-pep-440 (clauses)
(let (compatibles exclusions greater-equals less-thans unknowns)
(dolist (clause clauses)
(cond ((string-prefix-p "~=" clause)
(push (substring clause 2) compatibles))
((string-prefix-p "!=" clause)
(push (substring clause 2) exclusions))
((string-prefix-p ">=" clause)
(push (substring clause 2) greater-equals))
((string-prefix-p "<" clause)
(push (substring clause 1) less-thans))
(t (push clause unknowns))))
(list compatibles exclusions greater-equals less-thans unknowns)))
#+end_src
*** [[https://github.com/joaotavora/eglot][Eglot]] for [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]]
:PROPERTIES:
:CUSTOM_ID: sec:eglot-python