diff --git a/README.org b/README.org index 1a6d022..37a3a02 100644 --- a/README.org +++ b/README.org @@ -4977,6 +4977,11 @@ ruff "$@" | cat :header-args: :eval never-export :END: +Listing [[lst:shell-pip-list-outdated]] and [[lst:shell-pip-index-versions]] are two +examples of invoking [[https://pip.pypa.io/en/stable/][pip]] in =org-mode= =shell= source blocks with the respective +results in listing [[lst:shell-pip-list-outdated-results]] and +[[lst:shell-pip-index-versions-results]]. + #+caption[Show how to use =pip list --outdated=]: #+caption: Show how to use =pip list --outdated=. #+header: :wrap src text @@ -4988,6 +4993,7 @@ pip list --outdated #+caption[The result of =pip list --outdated=]: #+caption: The result of =pip list --outdated=. +#+name: lst:shell-pip-list-outdated-results #+RESULTS: lst:shell-pip-list-outdated #+begin_src text Package Version Latest Type @@ -5010,6 +5016,7 @@ pip index versions circular-buffer --no-color #+caption[The result of =pip index versions=]: #+caption: The result of =pip index versions=. +#+name: lst:shell-pip-index-versions-results #+RESULTS: lst:shell-pip-index-versions #+begin_src text circular-buffer (0.2.0) @@ -5057,14 +5064,22 @@ This invokes an asynchonous process and finishes with a message." #+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)" +(defgroup pip nil + "Client for accessing the \"Package Installer for Python\" and \"PyPI\"." + :group 'applications) + +(defcustom pip-frozen-packages nil + "Frozen Python packages." + :group 'pip :type '(repeat string)) +;; Frozen until the release of jupyterlab-4.0.0. +(setopt pip-frozen-packages '("aiofiles" + "jupyter-server-ydoc" + "jupyter-ydoc" + "y-py" + "ypy-websocket")) + (defun pip--upgrade-maybe-sentinel (process event) (when (and (eq (process-status process) 'exit) (buffer-live-p (process-buffer process))) @@ -5096,16 +5111,72 @@ buffer to check whether upgrading has made the dependencies incompatible." #+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)))))) +#+name: lst:pip-pypi-simple-json-api +#+begin_src emacs-lisp -n :lexical t :results silent +;; https://emacs.stackexchange.com/questions/61754/ +;; "How can I enable lexical binding for elisp code in Org mode?" -(defun pip-simple-json-project-list () +(defvar pip--simple-details-cache (make-hash-table :test 'equal) + "Cache for PyPI project details") + +(defun pip-simple-get-json () + (save-excursion + (goto-char (point-min)) + (and (re-search-forward "^HTTP/.+ 200 OK$" nil (line-end-position)) + (search-forward "\n\n" nil t) + (json-parse-buffer :array-type 'list)))) + +(defun pip-simple-details-retrieve (name callback &optional cbargs) + (pip--simple-details-prune-cache) + (if-let ((cached (gethash name pip--simple-details-cache))) + (apply callback (cdr cached) cbargs) + (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) + (let ((details (and (not (plist-get status :error)) + (pip-simple-get-json)))) + (when details + (setf (gethash name pip--simple-details-cache) + (cons (time-convert nil 'integer) details))) + (prog1 + (apply callback (or details 'error) cbargs) + (kill-buffer)))) + nil t)))) + +(defun pip--simple-details-prune-cache () + (let ((expired nil) + (time (- (time-convert nil 'integer) + ;; Ten minutes + (* 10 60)))) + (maphash (lambda (key val) + (when (< (car val) time) + (push key expired))) + pip--simple-details-cache) + (dolist (key expired) + (remhash key pip--simple-details-cache)))) +#+end_src + +#+caption[Using the Emacs interface to the =PyPI= simple =JSON= =API=]: +#+caption: Using the Emacs interface to the =PyPI= simple =JSON= =API=. +#+name: lst:using-pip-pypi-simple-json-api +#+begin_src emacs-lisp -n :lexical t :results silent +;; https://emacs.stackexchange.com/questions/61754/ +;; "How can I enable lexical binding for elisp code in Org mode?" + +(defun pip-simple-project-versions (name) + "Return the versions of Python package NAME." + (interactive "sPython package name: ") + (pip-simple-details-retrieve + name + (lambda (details) + (when-let ((versions (reverse (cdr (gethash "versions" details))))) + (message "`%s' versions: %S" name versions))))) + +(defun pip-simple-project-list () + "Get the Python package project list (PEP-691 and PEP-700)." (interactive) (let ((url-request-method "GET") (url-mime-accept-string "application/vnd.pypi.simple.latest+json"))