#+PROPERTY: HEADER-ARGS+ :eval no-export :exports both * Intro An org document with code blocks to help test the proposed patches. You need to load: #+begin_src elisp :results silent (load-file "my-async-tests.el") #+end_src Emacs and org versions: #+begin_src elisp (mapcar (lambda (sb) (list sb (symbol-value sb))) '(emacs-version org-version)) #+end_src #+RESULTS: | emacs-version | 30.0.50 | | org-version | 9.7-pre | Note that we've disabled eval on export: export doesn't know it needs to wait for asynchronous results. * A simple bash example :PROPERTIES: :header-args:bash: :execute-with my-shell-babel :nasync yes :END: The package `my-async-tests.el' contains the function `my-shell-babel-schedule' to evaluate shell script asynchronously. The header-args properties above request asynchronous execution for bash (:nasync yes), and, tells ob-core to use the prefix `my-shell-babel' when looking for functions to evaluate a source block. Thus, org will delegate execution to `my-shell-babel-schedule'. We don't have `my-shell-babel-execute', so, in this case, :nasync must be yes. A simple execution: #+begin_src bash date #+end_src #+RESULTS: : Fri Feb 16 18:11:08 CET 2024 A tricky computation takes some time: #+begin_src bash sleep 5; date #+end_src #+RESULTS: : Fri Feb 16 17:58:23 CET 2024 An example of a failure: #+begin_src bash sleepdd 1; false #+end_src #+RESULTS: * Python :PROPERTIES: :header-args:python: :execute-with my-org-babel :nasync yes :header-args:python+: :session py-async :END: Used =header-args= properties: - =:execute-with my-org-babel=: look for functions with the prefix `my-org-babel' to execute blocks (for the asynchronous case use `my-org-babel-schedule', and, for the synchronous case `my-org-babel-execute'). These functions are defined in [[file:my-async-tests.el]]. - =:nasync yes=: by default, execute asynchronously (use `my-org-babel-schedule'). - =:session py-async= by default, use a session named "py-async". ** basic examples *** async with a session A very simple test: #+begin_src python 2+3 #+end_src #+RESULTS: : 5 Let's import the module time in our session. #+begin_src python :results silent import time #+end_src #+RESULTS: (Yes, =:results silent= needs some work.) A table that requires some time to compute: #+begin_src python start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708103472.9s | 1708103473.9s | 1.0s | An error (click on the error , , to see the details): #+begin_src python 2/0 #+end_src #+RESULTS: *** async with no session :PROPERTIES: :header-args:python+: :session none :END: A very simple test: #+begin_src python 2+3 #+end_src #+RESULTS: : 5 Let's import the module time in our session. #+begin_src python :results silent import time #+end_src #+RESULTS: (Yes, =:results silent= needs some work.) A table that requires some time to compute: #+begin_src python start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708083470.9s | 1708083471.9s | 1.0s | Yes, it failed, as expected. "import time" was done in its own temporary session. The old result is preserved; the error is display as an overlay. Click on it to get more info about the error. Let's fix it, adding the import line: #+begin_src python import time start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708102948.9s | 1708102949.9s | 1.0s | An error (click on the error , , to see the details): #+begin_src python 2/0 #+end_src #+RESULTS: *** sync with a session :PROPERTIES: :header-args:python+: :session py-sync-session :nasync no :END: A very simple test: #+begin_src python 2+3 #+end_src #+RESULTS: : 5 Let's import the module time in our session. #+begin_src python :results silent import time #+end_src (Yes, =:results silent= needs some work.) A table that requires some time to compute: #+begin_src python start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708102997.5s | 1708102998.5s | 1.0s | An error (click on the error , , to see the details): #+begin_src python 2/0 #+end_src #+RESULTS: *** sync with no session :PROPERTIES: :header-args:python+: :session none :nasync no :END: A very simple test: #+begin_src python 2+3 #+end_src #+RESULTS: : 5 Let's import the module time in our session. #+begin_src python :results silent import time #+end_src (Yes, =:results silent= needs some work.) A table that requires some time to compute: #+begin_src python start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708083470.9s | 1708083471.9s | 1.0s | Yes, that fails (no session), displaying the details in a popup. Let's fix it: #+begin_src python import time start = time.time() time.sleep(1) end = time.time() ["%.1fs" % t for t in [start, end, end-start]] #+end_src #+RESULTS: | 1708103039.0s | 1708103040.0s | 1.0s | An error (click on the error , , to see the details): #+begin_src python 2/0 #+end_src #+RESULTS: ** worg examples Let's import matplotlib in our session. #+begin_src python import matplotlib import matplotlib.pyplot as plt #+end_src #+RESULTS: : None A figure in a PDF, asynchronous case. #+begin_src python :results file link fig=plt.figure(figsize=(3,2)) plt.plot([1,3,2]) fig.tight_layout() fname = 'myfig-async.pdf' plt.savefig(fname) fname # return this to org-mode #+end_src #+RESULTS: [[file:myfig-async.pdf]] A figure in a PDF, synchronous case. #+begin_src python :results file link :nasync no fig=plt.figure(figsize=(3,2)) plt.plot([1,3,2]) fig.tight_layout() fname = 'myfig-sync.pdf' plt.savefig(fname) fname # return this to org-mode #+end_src #+RESULTS: [[file:myfig-sync.pdf]] A PNG figure, asynchronous case. #+begin_src python :results graphics file output :file boxplot.png fig=plt.figure(figsize=(3,2)) plt.plot([1,3,2]) fig.tight_layout() fig #+end_src #+RESULTS: [[file:boxplot.png]] Same, but using the =:return= keyword. #+begin_src python :return "plt.gcf()" :results graphics file output :file boxplot.png fig=plt.figure(figsize=(3,2)) plt.plot([1,3,2]) fig.tight_layout() #+end_src #+RESULTS: [[file:boxplot.png]] Same, asynchronous but without a session this time. #+begin_src python :return "plt.gcf()" :results graphics file output :file boxplot-no-sess-a-y.png :session none import matplotlib import matplotlib.pyplot as plt fig=plt.figure(figsize=(3,2)) plt.plot([1,3,2]) fig.tight_layout() #+end_src #+RESULTS: [[file:boxplot-no-sess-a-y.png]] Lists are table, #+begin_src python [1,2,3] #+end_src #+RESULTS: | 1 | 2 | 3 | unless requested otherwise. #+begin_src python :results verbatim [1,2,3] #+end_src #+RESULTS: : [1, 2, 3] Dictionaries are tables too. #+begin_src python :results table {"a": 1, "b": 2} #+end_src #+RESULTS: | a | 1 | | b | 2 | Let's try the example with Panda. #+begin_src python :results none import pandas as pd import numpy as np #+end_src #+RESULTS: : None #+begin_src python :results table pd.DataFrame(np.array([[1,2,3],[4,5,6]]), columns=['a','b','c']) #+end_src #+RESULTS: | | a | b | c | |---+---+---+---| | 0 | 1 | 2 | 3 | | 1 | 4 | 5 | 6 | And the synchronous case? #+begin_src python :results table :nasync no pd.DataFrame(np.array([[1,2,3],[4,5,6]]), columns=['a','b','c']) #+end_src #+RESULTS: | | a | b | c | |---+---+---+---| | 0 | 1 | 2 | 3 | | 1 | 4 | 5 | 6 | Without session ? #+begin_src python :results table :session none pd.DataFrame(np.array([[1,2,3],[4,5,6]]), columns=['a','b','c']) #+end_src #+RESULTS: | | a | b | c | |---+---+---+---| | 0 | 1 | 2 | 3 | | 1 | 4 | 5 | 6 | Right, we need to import the libraries (no session). #+begin_src python :results table :session none import pandas as pd import numpy as np pd.DataFrame(np.array([[1,2,3],[4,5,6]]), columns=['a','b','c']) #+end_src #+RESULTS: | | a | b | c | |---+---+---+---| | 0 | 1 | 2 | 3 | | 1 | 4 | 5 | 6 | ** inline examples A simple asynchronous inline src_python{3*2} {{{results(=6=)}}}. An other one containing a mistake src_python{2/0} {{{results(=6=)}}} (click on the error to see the details). Some very slow inline asynchronous computations that all run in the same session. You need to execute the 3 of them at once. Here is the first one src_python[:return "\"OK1\""]{import time; time.sleep(5)} {{{results(=OK1=)}}} and a second one src_python[:return "\"OK1 bis\""]{import time; time.sleep(5)} {{{results(=OK1 bis=)}}} and the third one src_python[:return "\"OK2\""]{import time; time.sleep(5)} {{{results(=OK2=)}}}. Yes, the previous paragraph is unreadable; it's on purpose, to check that ob-core can figure it out. Let's repeat, in a more readable way, and making the last one synchronous. Some very slow inline computations that all run in the same session. Here is the first asynchronous one src_python[:return"\"OK1\""]{import time; time.sleep(5)} {{{results(=None=)}}} and a second one, asynchronous too: src_python[:return "\"OK1 bis\""]{import time; time.sleep(5)} {{{results(=OK1 bis=)}}} and finally, a third one, synchronous this one: src_python[:nasync no :return "\"OK2\""]{import time; time.sleep(5)} {{{results(=OK2=)}}}. Note that, once the user executes the last synchronous block, the user is blocked until the synchronous execution can start (i.e. all previous asynchronous executions are done) and until it's done. The display is updated though, to see the asynchronous progress.