Add a "Package Installer for Python" section
This commit is contained in:
parent
97cc936aee
commit
402f89d7fe
226
README.org
226
README.org
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user