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

No suitable parser found to handle request body type application/vnd.ms-excel. #959

Closed
chuxinyuan opened this issue Jul 10, 2024 · 8 comments · Fixed by #975
Closed

No suitable parser found to handle request body type application/vnd.ms-excel. #959

chuxinyuan opened this issue Jul 10, 2024 · 8 comments · Fixed by #975

Comments

@chuxinyuan
Copy link

chuxinyuan commented Jul 10, 2024

Description

I want to upload the data in .csv format, but I had trouble deploying it to the server.

Code

plumber_run.R

library(plumber)
pr("plumber.R") %>%
  pr_run(host = "0.0.0.0", port = 8001)

plumber.R

#* @apiTitle  render report API

#* upload csv file
#* @post /upload
#* @parser multi
#* @parser csv
#* @param f:file
\(f) {
  data = read.csv(f[[1]])
  print(data)
}

Data

data = data.frame(
  name = c("AAA", "BBB", "CCC"),
  ID = c("001", "002", "003"),
  address = c("Peking", "New York", "Paris")
)
write.csv(data, "data.csv", row.names = FALSE)

Session info

R version 4.2.2 Patched (2022-11-10 r83330)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 12 (bookworm), RStudio 2022.7.1.554

Locale:
  LC_CTYPE=zh_CN.UTF-8       LC_NUMERIC=C               LC_TIME=zh_CN.UTF-8       
  LC_COLLATE=zh_CN.UTF-8     LC_MONETARY=zh_CN.UTF-8    LC_MESSAGES=en_US.UTF-8   
  LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
  LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

Package version:
  askpass_1.2.0     base64enc_0.1.3   bslib_0.7.0       cachem_1.1.0      cli_3.6.2        
  compiler_4.2.2    crayon_1.5.2      curl_5.2.1        data.table_1.15.4 digest_0.6.35    
  ellipsis_0.3.2    evaluate_0.23     fansi_1.0.6       fastmap_1.2.0     fontawesome_0.5.2
  fs_1.6.4          glue_1.7.0        graphics_4.2.2    grDevices_4.2.2   highr_0.11       
  htmltools_0.5.8.1 httpuv_1.6.15     httr_1.4.7        jquerylib_0.1.4   jsonlite_1.8.8   
  knitr_1.47        later_1.3.2       lifecycle_1.0.4   magrittr_2.0.3    memoise_2.0.1    
  methods_4.2.2     mime_0.12         openssl_2.2.0     pillar_1.9.0      pkgconfig_2.0.3  
  plumber_1.2.2     promises_1.3.0    purrr_1.0.2       R6_2.5.1          rapidoc_9.3.4    
  rappdirs_0.3.3    Rcpp_1.0.12       rlang_1.1.3       rmarkdown_2.27    rstudioapi_0.16.0
  sass_0.4.9        sodium_1.3.1      stats_4.2.2       stringi_1.8.4     swagger_5.17.14.1
  sys_3.4.2         tibble_3.2.1      tinytex_0.51      tools_4.2.2       utf8_1.2.4       
  utils_4.2.2       vctrs_0.6.5       webutils_1.2.0    xfun_0.44         yaml_2.3.8  
@chuxinyuan chuxinyuan changed the title How do I proceed after the csv file is uploaded No suitable parser found to handle request body type application/vnd.ms-excel. Jul 10, 2024
@chuxinyuan chuxinyuan changed the title No suitable parser found to handle request body type application/vnd.ms-excel. An error occurred while rendering the document on the server "error": "500 - Internal server error Jul 11, 2024
@chuxinyuan chuxinyuan changed the title An error occurred while rendering the document on the server "error": "500 - Internal server error No suitable parser found to handle request body type application/vnd.ms-excel. Jul 11, 2024
@thomasp85
Copy link
Collaborator

thomasp85 commented Jan 23, 2025

How are you sending the data? If you send the data as a .csv the body type should not be reported as application/vnd.ms-excel?

@schloerke
Copy link
Collaborator

Similar to

#* @serializer contentType list(type="application/pdf")
, you'll need to set #* @serializer contentType list(type="application/vnd.ms-excel").

You'll also need to transform your output into an excel file before returning it in your function. (I do not know how to do this last step.)

@schloerke
Copy link
Collaborator

A possible approach which adds a xlsx serializer: #793 (comment)

# untested!

register_serializer(
  "xlsx",
  function(..., type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
    if (!requireNamespace("writexl", quietly = TRUE)) {
      stop("The writexl package is not available but is required in order to use the writexl serializer",
           call. = FALSE)
  }

  serializer_write_file(
    # The temp file should end in `.xlsx`
    fileext = ".xlsx",
    # Pass through your content type
    type = type,
    write_fn = function(val, tmpfile) {
      writexl::write_xlsx(x = val, path =tmpfile, ...)
    }
  )
})

@r2evans
Copy link
Contributor

r2evans commented Jan 23, 2025

@schloerke I thought serializer_* functions handled rendering output, not handling input which is what I think the issue is asking,

I want to upload the data in .csv format

@chuxinyuan I think you don't need to use read.csv at all, the use of @parser should handle that for you. (For this reprex, I created a CSV file with the top few rows of mtcars.)

#* @post /upload
#* @param f:file
#* @parser multi
#* @parser csv
function(req, res, f) {
  browser() # for demonstration
  data <- f[[1]]
  data
}

Execution:

print(f)
# $out.csv
# # A tibble: 3 × 11
#     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1  21       6   160   110  3.9   2.62  16.5     0     1     4     4
# 2  21       6   160   110  3.9   2.88  17.0     0     1     4     4
# 3  22.8     4   108    93  3.85  2.32  18.6     1     1     4     1

So you can use f[[1]] directly to access the already-parsed data frame.

FYI, the command-line for uploading the file (for this test):

curl -X 'POST' 'http://127.0.0.1:9999/v1/upload' -F '[email protected];type=text/csv'

Alternatively, if you want to be able to work with the raw CSV string, remove the @parsers and use read.csv(text = f[[1]]):

A simple reprex:

#* @post /upload
#* @param f:file
function(req, res, f) {
  browser()
  data <- read.csv(text = f[[1]])
  data
}

Execution:

print(f)
# $out.csv
# [1] "mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb\n21,6,160,110,3.9,2.62,16.46,0,1,4,4\n21,6,160,110,3.9,2.875,17.02,0,1,4,4\n22.8,4,108,93,3.85,2.32,18.61,1,1,4,1\n"

data <- read.csv(f[[1]])
# Warning in file(file, "rt") :
#   cannot open file 'mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
# 21,6,160,110,3.9,2.62,16.46,0,1,4,4
# 21,6,160,110,3.9,2.875,17.02,0,1,4,4
# 22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
# ': No such file or directory
# Error in file(file, "rt") : cannot open the connection

data <- read.csv(text = f[[1]])
print(data)
#    mpg cyl disp  hp drat    wt  qsec vs am gear carb
# 1 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
# 2 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
# 3 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1

... and then do with data as you need.

@r2evans
Copy link
Contributor

r2evans commented Jan 23, 2025

A possible approach which adds a xlsx serializer: #793 (comment)

untested!

register_serializer(
"xlsx",
function(..., type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
if (!requireNamespace("writexl", quietly = TRUE)) {
stop("The writexl package is not available but is required in order to use the writexl serializer",
call. = FALSE)
}

serializer_write_file(
# The temp file should end in .xlsx
fileext = ".xlsx",
# Pass through your content type
type = type,
write_fn = function(val, tmpfile) {
writexl::write_xlsx(x = val, path =tmpfile, ...)
}
)
})

@schloerke seems like a simple PR with this code might be welcome? (Unrelated to this issue.)

@schloerke
Copy link
Collaborator

@r2evans Yes, Thomas and I both mis-read as we were just talking about serializers. Thank you! **Re-opening


@schloerke seems like a simple PR with this code might be welcome? (Unrelated to this issue.)

@r2evans Yes, can you please make a PR?

@schloerke schloerke reopened this Jan 23, 2025
@schloerke
Copy link
Collaborator

@chuxinyuan I believe you want something similar to...

# untested

parser_xlsx <- function(...) {
  parser_read_file(function(tmpfile) {
    read_xl:: read_excel(tmpfile, ...)
  })
}

plumber::register_parser("xlsx", parser_xlsx, fixed = "application/vnd.ms-excel", regex = "^application/vnd\.ms-excel")

@r2evans When a parser can be paired with a serializer, it makes for a good combo. Can I ask for you to include it as well? Thank you!

@r2evans
Copy link
Contributor

r2evans commented Jan 23, 2025

Can I ask for you to include it as well?

Already working on it :-)

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

Successfully merging a pull request may close this issue.

4 participants