Skip to content

Commit

Permalink
Remap output streams when kniting the document.
Browse files Browse the repository at this point in the history
  • Loading branch information
dfalbel committed Jul 12, 2023
1 parent e80fa43 commit 19206ad
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 12 deletions.
48 changes: 44 additions & 4 deletions R/output.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


remap_output_streams <- function() {
output <- import("rpytools.output")

Expand All @@ -10,11 +8,53 @@ remap_output_streams <- function() {
if (!is.na(remap))
force <- identical(remap, "1")

if (!force) return()
set_output_streams(tty = interactive() || isatty(stdout()))
}

set_output_streams <- function(tty) {
output <- import("rpytools.output")
output$remap_output_streams(
write_stdout,
write_stderr,
tty = interactive() || isatty(stdout()),
force = force
tty = tty
)
}

reset_output_streams <- function() {
output <- import("rpytools.output")
output$reset_output_streams()
remap_output_streams()
}

set_knitr_python_stdout_hook <- function() {
# we don't want to to force a knitr load namespace, so if it's already loaded
# we set the knitr hook, otherwise we schedule an onLoad hook.
if (isNamespaceLoaded("knitr")) {
set_knitr_hook()

# if knitr is already in progress here, this means that python was initialized
# during a chunk execution. We have to force an instant remap as the hook won't
# have a chance to run for that chunk.
if (isTRUE(getOption('knitr.in.progress'))) set_output_streams(tty = FALSE)
} else {
setHook(
packageEvent("knitr", "onLoad"),
function(...) {
set_knitr_hook()
}
)
}
}

set_knitr_hook <- function() {
knitr::knit_hooks$set(include = function(before, options, envir) {
if (!options$include) return()
if (before) {
set_output_streams(tty = FALSE)
} else {
reset_output_streams()
}
})
}

1 change: 1 addition & 0 deletions R/package.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ ensure_python_initialized <- function(required_module = NULL) {

# remap output streams to R output handlers
remap_output_streams()
set_knitr_python_stdout_hook()

# generate 'R' helper object
py_inject_r()
Expand Down
17 changes: 9 additions & 8 deletions inst/python/rpytools/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,16 @@ def flush(self):
return None


def remap_output_streams(r_stdout, r_stderr, tty, force):

if (force or sys.stdout is None):
sys.stdout = OutputRemap(sys.stdout, r_stdout, tty)

if (force or sys.stderr is None):
sys.stderr = OutputRemap(sys.stderr, r_stderr, tty)

def remap_output_streams(r_stdout, r_stderr, tty):
sys.stdout = OutputRemap(sys.stdout, r_stdout, tty)
sys.stderr = OutputRemap(sys.stderr, r_stderr, tty)

def reset_output_streams():
# https://stackoverflow.com/a/51340381/3297472 suggests that the initial stdout
# is always available in `sys.__stdout__` so we just need to reassign to revert
# it.
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__



Expand Down
4 changes: 4 additions & 0 deletions tests/testthat/_snaps/python-knitr-engine/knitr-print.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
py <- reticulate::import_builtins()
py$print("Hello world")

## Hello world
10 changes: 10 additions & 0 deletions tests/testthat/resources/knitr-print.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: "Preserve output from python print"
output: md_document
---

```{r}
py <- reticulate::import_builtins()
py$print("Hello world")
```

25 changes: 25 additions & 0 deletions tests/testthat/test-python-knitr-engine.R
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,28 @@ test_that("knitr 'warning=FALSE' option", {
expect_no_match(res, "UserWarning", fixed = TRUE)

})

test_that("Output streams are remaped when kniting", {

skip_on_cran()
skip_if_not_installed("rmarkdown")
local_edition(3)

owd <- setwd(test_path("resources"))
rmarkdown::render("knitr-print.Rmd")
setwd(owd)

rendered <- test_path("resources", "knitr-print.md")
expect_snapshot_file(rendered)

# if remaping is set by default we have no way to check that the options
# is correctly reset
skip_if(!is.na(Sys.getenv("RETICULATE_REMAP_OUTPUT_STREAMS", unset = NA)))

py <- reticulate::import_builtins()
x <- py_capture_output(out <- capture.output({
py$print("hello world")
}))
expect_length(out, 0)

})

0 comments on commit 19206ad

Please sign in to comment.