Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate path as shiny app #4091

Open
JosiahParry opened this issue Jun 24, 2024 · 11 comments
Open

Validate path as shiny app #4091

JosiahParry opened this issue Jun 24, 2024 · 11 comments

Comments

@JosiahParry
Copy link

The shiny::runApp() function is very flexible and will run an app from a path e.g. app.R www/index.html or from a directory that contains a ui.R and server.R function.

It would be great to have a function to validate that a path is indeed a shiny app without having to run runApp() and catch if an error occurs.

@gadenbuie
Copy link
Member

If this is for a third-party tool to help run Shiny apps, I'd recommend either requiring your users to follow a naming convention established by your tool or to ask users to explicitly name the primary file containing the Shiny app.

Unfortunately, when shiny::runApp() is given an .R file that purports to produce a Shiny application, it'd be very difficult to confirm that the .R file indeed creates a Shiny app without actually running the code.

@JosiahParry
Copy link
Author

Understood. Do you have a suggestion on how to try validating a shiny app via shiny::runApp()? The hard part is that it's blocking. 🤔

@gadenbuie
Copy link
Member

You could maybe start the app in a background R session with callr? You could also source the app script and inspect the return value. According to the runApp() docs it should be a shiny app object or a list with ui and server components (if it doesn't follow naming conventions).

If you can describe your use case a little more, I might be able to provide some more targeted advice. 😄

@JosiahParry
Copy link
Author

The goal is to prepare a shiny app for deployment. The directory is zipped, copied, and moved else where. Then from that location shiny::runApp() is called supplying only the directory path as that is the most flexible (based on doc).

However before all of that stuff is done, my goal is to verify that the app is indeed a certified runnable shiny app!

@JosiahParry
Copy link
Author

JosiahParry commented Jun 24, 2024

{callr} is a good helper. thank you! I think this is sufficient and quite handy. Though I'm sure i'm missing something with the error and listening on check.

iis_shiny_app <- function(app_dir, max_check_time = 10L, poll_duration = 5000L) {
  # store when check first starts
  start_check_time <- Sys.time()

  # helper function to start shiny app on random port
  run_app <- function(app_dir) shiny::runApp(app_dir, port = httpuv::randomPort())

  # start app in background process
  rp <- callr::r_bg(run_app, list(app_dir = app_dir))

  # set polling duration (5 sec default)
  .poll_res <- rp$poll_io(poll_duration) # set polling duration
  while (TRUE) {
    # read a line from the output
    line <- rp$read_error_lines()
    # if there is output check
    if (length(line) > 0) {
      error_detected <- any(grepl("Error in shinyAppDir", line))
      app_start_detected <- any(grepl("Listening on", line))
      if (error_detected || app_start_detected) {
        break
      }
    }
    # check the time diff
    check_length <- difftime(
      Sys.time(),
      start_check_time,
      units = "secs"
    )

    # if ther time check is exceeded, error out
    if (check_length > max_check_time) {
      cli::cli_abort(
        c(
          "Cannot determine if valid shiny app",
          i = "Waited {max_check_time} seconds."
        )
      )
    }
  }

  # kill the process
  .killed_process <- rp$kill()

  # return the check
  !error_detected
}

@ismirsehregal
Copy link
Contributor

ismirsehregal commented Jul 3, 2024

Why don't you use shinyAppDir()?

It will throw an error if the directory doesn't contain a vaild shiny app, which you could tryCatch.

library(shiny)
try_app <- tryCatch({shinyAppDir("C:/path/to/shiny/app")}, error = function(e) {e})
is.shiny.appobj(try_app)

@JosiahParry
Copy link
Author

@ismirsehregal because in the case of success the function will run indefinitely

@ismirsehregal
Copy link
Contributor

ismirsehregal commented Jul 3, 2024

@JosiahParry: No it won't, as shinyAppDir doesn't run the app, it only creates a shiny app object which you need to print or directly pass to runApp to be executed (just as shinyApp()).

Please see ?shinyAppDir

Value: An object that represents the app. Printing the object or passing it to runApp() will run the app.


Example:

library(shiny)
valid_path <- file.path(find.package("shiny"), "examples", "01_hello")
invalid_path <- file.path(find.package("shiny"), "examples", "invalid")
try_app <- tryCatch({shinyAppDir(valid_path)}, error = function(e) {e})
if (is.shiny.appobj(try_app)) {
  message("This is a valid shiny app directory.")
  if (interactive()) {
    run <- readline(message("Type 'yes' or 'y' to run it: "))
    if (tolower(run) == "yes" || tolower(run) == "y") {
      # print(try_app) # alternative
      runApp(try_app)
    }
  }
} else {
  message("This is not a shiny app.")
}

@JosiahParry
Copy link
Author

@ismirsehregal thank you for this hint! This works much better:

is_shiny_app <- function(path) {
    tryCatch(
      !is.null(shiny::shinyAppDir(path)),
      error = function(cond) FALSE
    )
}

@gadenbuie
Copy link
Member

Thanks @ismirsehregal and @JosiahParry, that's an elegant solution!

@JosiahParry
Copy link
Author

I feel that this has been resolved as a one off matter but the shiny package would benefit from having an official way to do this.

I’d respectfully request that this issue be reopened as a feature request.

Thanks!

@gadenbuie gadenbuie reopened this Jul 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants