How to override exported function from R package listed in Imports
Asked Answered
A

2

6

My package's DESCRIPTION file has httr in its Imports directive:

Imports:
    httr (>= 1.1.0),
    jsonlite,
    rstudioapi

httr exports an S3method for length.path.

S3method(length,path)

And it's defined as:

#' @export
length.path <- function(x) file.info(x)$size

In my package I have objects that I assign the class "path" to. Every time I assign the class "path" to any object, regardless of whether or not I ever call length() on the object, this is printed to stdout:

Error in file.info(x) : invalid filename argument

Here's some reproducible code everyone can run:

> sessionInfo()
R version 3.3.1 (2016-06-21)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.11.5 (El Capitan)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] tools_3.3.1

> thing = 1:5
> class(thing) = 'path'

> requireNamespace('httr')
Loading required namespace: httr

> sessionInfo()
R version 3.3.1 (2016-06-21)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.11.5 (El Capitan)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] httr_1.2.1  R6_2.1.2    tools_3.3.1

> thing = 1:5
> class(thing) = 'path'
Error in file.info(x) : invalid filename argument

I've tried catching it in a try but that doesn't work:

set_class = function(obj, c) {
  class(obj) = c
  return(obj)
}

thing = 1:5
thing = try(set_class(thing, 'path'), silent=TRUE)

Yields:

Error in file.info(x) : invalid filename argument

I've tried assignInNamespace to override the function:

base_length = function(obj) {
  return(base::length(obj))
}

assignInNamespace('length.path', base_length, 'httr')
thing = 1:5
class(thing) = 'path'

But I get Error: evaluation nested too deeply: infinite recursion / options(expressions=)?

When I use httr functions in my package I use them with httr::function so I'm not sure how this length.path function is leaking into my namespace and overriding the base length function. I've also tried explicit @importFrom httr function for each function that I use instead of using httr::function but that doesn't work either.

I also found this:

https://support.bioconductor.org/p/79059/

But the solution seemed to be to edit the source code of httr, which I can't do since my package imports it. How can I get around this?

Astereognosis answered 27/8, 2016 at 19:53 Comment(6)
If length() is a generic, then you want to call base::length.default() to get the default method.Mil
The problem is I never call length. This happens when I call class(obj) = 'path'Astereognosis
I think class(obj) = 'path' only gives an error within RStudio or similar IDEs where you can can basically see the output of str(...) for your workspace variables all the time. When you assign a new class the IDE updates this description and calls length in the process. In the "normal" RGui the error does not occur.Absinthe
Ahh that must be it, then. Still, I should be able to override that function somehow, right?Astereognosis
You should complain to the author of the method because overriding length in that way is a really bad idea! (ie. file an issue on GitHub and we'll fix it in httr)Herbart
Creating an issue to change it did cross my mind but I figured there must be some way for me to manage the namespaces of the packages my package imports. Asking a maintainer to change something every time there's a conflict doesn't seem very scaleable. Anyway, I created the issue.Astereognosis
A
1

One possibility could be to create a function length.path() in your own package. If the base type of your path-objects is compatible with base::length() you could just unclass it to avoid infinite recursion:

length.path <- function(x) length(unclass(x))

However, this is potentially slow compared to a direct call to base::length() because it copies the object and needs method dispatch two times.

Absinthe answered 27/8, 2016 at 21:22 Comment(6)
Hmm, doesn't seem to work. Added that verbatim to my package and still seeing the error to stdout. Do I need to export it perhaps? The base type of my path object is a list, so I think it should work fine.Astereognosis
Are you sure the error is created by the Imports: httr in the DESCRIPTION file? I just tried to create a dummy package that imports httr and contains just one function that returns an object of class path. Everything works. Only when I call library(httr) after loading the dummy package the error arises.Absinthe
It doesn't seem to happen until I use a function in my package that uses httr. So if I library(RNeo4j) and then sessionInfo(), httr namespace isn't loaded, but as soon as I use a function from my package that uses httr::func internally, I see httr in sessionInfo(). And that's when I can reproduce the file.info error.Astereognosis
Of course, my bad -.- exporting the above function with export("length.path") works for me. Declaring it as S3method("length", "path") does not.Absinthe
Still not working. :( I have the function in my NAMESPACE under export(length.path) and still seeing the error to stdout.Astereognosis
I've accepted your answer because it was mostly your unclass idea that got me through but I've added an answer for what worked for me specifically.Astereognosis
A
0

The solution for me was to add an .onLoad function. Thanks to AEF's answer for the unclass idea.

#' @importFrom utils assignInNamespace
.onLoad = function(libname, pkgname) {
  length.path = function(obj) {
    return(length(unclass(obj)))
  }

  assignInNamespace('length.path', length.path, 'httr')
}

It raises a NOTE in R CMD check --as-cran but I'm hoping to get away with it.

Astereognosis answered 28/8, 2016 at 3:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.