forked from mirrors/org-mode
ob-python: Fix several issues with :session :results value
* lisp/ob-python.el (org-babel-python-evaluate-session): Fix a few related issues with :session :results value blocks, including broken if-else statements, indented blocks with blank lines, and returning the wrong value when underscore has been used. (org-babel-python--eval-ast): New constant variable, a string consisting of Python code to execute a source block using ast. Previously, python blocks with parameters ":session :results value" were entered line-by-line into the Python session, which could cause issues around indentation and new lines. Now, such python blocks are written to temp files, then the built-in ast python module is used to parse and execute them, and to extract the last line separately to return as a result. Introduces a change in behavior, requiring that the last line must be a top-level expression statement if its result is to be saved (otherwise, the result is None).
This commit is contained in:
parent
e076ed6e85
commit
cc89d5523f
|
@ -11,6 +11,15 @@ See the end of the file for license conditions.
|
||||||
Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
|
Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
|
||||||
|
|
||||||
* Version 9.4 (not yet released)
|
* Version 9.4 (not yet released)
|
||||||
|
** Incompatible changes
|
||||||
|
*** Python session return values must be top-level expression statements
|
||||||
|
|
||||||
|
Python blocks with ~:session :results value~ header arguments now only
|
||||||
|
return a value if the last line is a top-level expression statement,
|
||||||
|
otherwise the result is None. Also, None will now show up under
|
||||||
|
"#+RESULTS:", as it already did with ~:results value~ for non-session
|
||||||
|
blocks.
|
||||||
|
|
||||||
** New features
|
** New features
|
||||||
|
|
||||||
*** Numeric priorities are now allowed (up to 65)
|
*** Numeric priorities are now allowed (up to 65)
|
||||||
|
|
|
@ -247,6 +247,25 @@ open('%s', 'w').write( pprint.pformat(main()) )")
|
||||||
")); "
|
")); "
|
||||||
"__org_babel_python_fh.close()"))
|
"__org_babel_python_fh.close()"))
|
||||||
|
|
||||||
|
(defconst org-babel-python--eval-ast "\
|
||||||
|
import ast
|
||||||
|
try:
|
||||||
|
with open('%s') as f:
|
||||||
|
__org_babel_python_ast = ast.parse(f.read())
|
||||||
|
__org_babel_python_final = __org_babel_python_ast.body[-1]
|
||||||
|
if isinstance(__org_babel_python_final, ast.Expr):
|
||||||
|
__org_babel_python_ast.body = __org_babel_python_ast.body[:-1]
|
||||||
|
exec(compile(__org_babel_python_ast, '<string>', 'exec'))
|
||||||
|
__org_babel_python_final = eval(compile(ast.Expression(
|
||||||
|
__org_babel_python_final.value), '<string>', 'eval'))
|
||||||
|
else:
|
||||||
|
exec(compile(__org_babel_python_ast, '<string>', 'exec'))
|
||||||
|
__org_babel_python_final = None
|
||||||
|
except Exception:
|
||||||
|
from traceback import format_exc
|
||||||
|
__org_babel_python_final = format_exc()
|
||||||
|
raise")
|
||||||
|
|
||||||
(defun org-babel-python-evaluate
|
(defun org-babel-python-evaluate
|
||||||
(session body &optional result-type result-params preamble)
|
(session body &optional result-type result-params preamble)
|
||||||
"Evaluate BODY as Python code."
|
"Evaluate BODY as Python code."
|
||||||
|
@ -294,32 +313,9 @@ If RESULT-TYPE equals `output' then return standard output as a
|
||||||
string. If RESULT-TYPE equals `value' then return the value of the
|
string. If RESULT-TYPE equals `value' then return the value of the
|
||||||
last statement in BODY, as elisp."
|
last statement in BODY, as elisp."
|
||||||
(let* ((send-wait (lambda () (comint-send-input nil t) (sleep-for 0 5)))
|
(let* ((send-wait (lambda () (comint-send-input nil t) (sleep-for 0 5)))
|
||||||
(dump-last-value
|
|
||||||
(lambda
|
|
||||||
(tmp-file pp)
|
|
||||||
(mapc
|
|
||||||
(lambda (statement) (insert statement) (funcall send-wait))
|
|
||||||
(if pp
|
|
||||||
(list
|
|
||||||
"import pprint"
|
|
||||||
(format "open('%s', 'w').write(pprint.pformat(_))"
|
|
||||||
(org-babel-process-file-name tmp-file 'noquote)))
|
|
||||||
(list (format "open('%s', 'w').write(str(_))"
|
|
||||||
(org-babel-process-file-name tmp-file
|
|
||||||
'noquote)))))))
|
|
||||||
(last-indent 0)
|
(last-indent 0)
|
||||||
(input-body (lambda (body)
|
(input-body (lambda (body)
|
||||||
(dolist (line (split-string body "[\r\n]"))
|
(dolist (line (split-string body "[\r\n]"))
|
||||||
;; Insert a blank line to end an indent
|
|
||||||
;; block.
|
|
||||||
(let ((curr-indent (string-match "\\S-" line)))
|
|
||||||
(if curr-indent
|
|
||||||
(progn
|
|
||||||
(when (< curr-indent last-indent)
|
|
||||||
(insert "")
|
|
||||||
(funcall send-wait))
|
|
||||||
(setq last-indent curr-indent))
|
|
||||||
(setq last-indent 0)))
|
|
||||||
(insert line)
|
(insert line)
|
||||||
(funcall send-wait))
|
(funcall send-wait))
|
||||||
(funcall send-wait)))
|
(funcall send-wait)))
|
||||||
|
@ -344,17 +340,35 @@ last statement in BODY, as elisp."
|
||||||
(funcall send-wait))
|
(funcall send-wait))
|
||||||
2) "\n")))
|
2) "\n")))
|
||||||
(`value
|
(`value
|
||||||
(let ((tmp-file (org-babel-temp-file "python-")))
|
(let ((tmp-results-file (org-babel-temp-file "python-"))
|
||||||
|
(body (let ((tmp-src-file (org-babel-temp-file
|
||||||
|
"python-")))
|
||||||
|
(with-temp-file tmp-src-file (insert body))
|
||||||
|
(format org-babel-python--eval-ast
|
||||||
|
tmp-src-file))))
|
||||||
(org-babel-comint-with-output
|
(org-babel-comint-with-output
|
||||||
(session org-babel-python-eoe-indicator nil body)
|
(session org-babel-python-eoe-indicator nil body)
|
||||||
(let ((comint-process-echoes nil))
|
(let ((comint-process-echoes nil))
|
||||||
(funcall input-body body)
|
(funcall input-body body)
|
||||||
(funcall dump-last-value tmp-file
|
(dolist
|
||||||
(member "pp" result-params))
|
(statement
|
||||||
|
(if (member "pp" result-params)
|
||||||
|
(list
|
||||||
|
"import pprint"
|
||||||
|
(format "open('%s', 'w').write(pprint.pformat(\
|
||||||
|
__org_babel_python_final))"
|
||||||
|
(org-babel-process-file-name
|
||||||
|
tmp-results-file 'noquote)))
|
||||||
|
(list (format "open('%s', 'w').write(str(\
|
||||||
|
__org_babel_python_final))"
|
||||||
|
(org-babel-process-file-name
|
||||||
|
tmp-results-file 'noquote)))))
|
||||||
|
(insert statement)
|
||||||
|
(funcall send-wait))
|
||||||
(funcall send-wait) (funcall send-wait)
|
(funcall send-wait) (funcall send-wait)
|
||||||
(insert org-babel-python-eoe-indicator)
|
(insert org-babel-python-eoe-indicator)
|
||||||
(funcall send-wait)))
|
(funcall send-wait)))
|
||||||
(org-babel-eval-read-file tmp-file))))))
|
(org-babel-eval-read-file tmp-results-file))))))
|
||||||
(unless (string= (substring org-babel-python-eoe-indicator 1 -1) results)
|
(unless (string= (substring org-babel-python-eoe-indicator 1 -1) results)
|
||||||
(org-babel-result-cond result-params
|
(org-babel-result-cond result-params
|
||||||
results
|
results
|
||||||
|
|
|
@ -138,6 +138,41 @@ if True:
|
||||||
(org-babel-execute-maybe)
|
(org-babel-execute-maybe)
|
||||||
(org-babel-execute-src-block)))))
|
(org-babel-execute-src-block)))))
|
||||||
|
|
||||||
|
(ert-deftest test-ob-python/if-else-block ()
|
||||||
|
(should
|
||||||
|
(equal "success" (org-test-with-temp-text "#+begin_src python :session :results value
|
||||||
|
value = 'failure'
|
||||||
|
if False:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
value = 'success'
|
||||||
|
value
|
||||||
|
#+end_src"
|
||||||
|
(org-babel-execute-src-block)))))
|
||||||
|
|
||||||
|
(ert-deftest test-ob-python/indent-block-with-blank-lines ()
|
||||||
|
(should
|
||||||
|
(equal 20
|
||||||
|
(org-test-with-temp-text "#+begin_src python :session :results value
|
||||||
|
foo = 0
|
||||||
|
for i in range(10):
|
||||||
|
foo += 1
|
||||||
|
|
||||||
|
foo += 1
|
||||||
|
|
||||||
|
foo
|
||||||
|
#+end_src"
|
||||||
|
(org-babel-execute-src-block)))))
|
||||||
|
|
||||||
|
(ert-deftest test-ob-python/assign-underscore ()
|
||||||
|
(should
|
||||||
|
(equal "success"
|
||||||
|
(org-test-with-temp-text "#+begin_src python :session :results value
|
||||||
|
_ = 'failure'
|
||||||
|
'success'
|
||||||
|
#+end_src"
|
||||||
|
(org-babel-execute-src-block)))))
|
||||||
|
|
||||||
(provide 'test-ob-python)
|
(provide 'test-ob-python)
|
||||||
|
|
||||||
;;; test-ob-python.el ends here
|
;;; test-ob-python.el ends here
|
||||||
|
|
Loading…
Reference in New Issue