rticulate / import

An Import Mechanism For R
https://import.rticulate.org
Other
222 stars 14 forks source link

`import::from` does not update when a nested file has been edited #77

Closed vdanglse closed 1 year ago

vdanglse commented 1 year ago

First of all, thank you for the amazing package. I have greatly enjoyed using it. However, I have an issue with nesting import statements which I can't quite solve.

I have the following folder structure:

File 1: main.R

import::from("module.R", f) 
f()

File 2: module.R

import::here("submodule.R", g)
f <- \() g()

File 3: submodule.R

g <- \(){1}

The first time I run main.R, it returns 1 as expected. However, if I change the definition of g in submodule.R to g <- \(){2} and rerun main.R, the result is still 1. I figured that import::from would check module.R to see if it has been changed before reimporting f. If I make some superficial change to module.R (e.g. adding extra linebreaks) before running main.R, it would return the correct result.

Would there be an easy way to overcome this, and is there something that I'm missing here?

torfason commented 1 year ago

I'm afraid this is one of the hard things ("cache invalidation and naming things"). More specifically, this is a classic recursive caching issue.

The problem is that import checks any module script referenced in import::from() for file modification time (see here). But in your case, because module.R has not changed, that code is never run, so submodule.R is never checked for modification time.

I believe that solving this in an automatic way would require full awareness of any recursive calls inside a module and would quickly evolve into a full pipeline tool (such as the https://github.com/ropensci/targets/ package). So unfortunately, I don't expect that it will make its way into the package.

vdanglse commented 1 year ago

Thank you for clarifying. In this case, is there an easy way to override the caching and force import::from to run as if we restarted the R session?

torfason commented 1 year ago

Well, out of curiosity, I did check out ways to do this doing this. First it is important to note that recursive imports are only supported when the inner import uses import::here() (as your example does). This is because the inner modules functions end up in strange places (in terms of R environments).

For the case where the outer module does that (use import::here() to import the inner module), but then you use import::from() to import the outer module, the following code should work.

> import::from(module_recursive_outer_here.R, print_title_text)
> print_title_text("hi friend, how are you")
[1] "Hi Friend, How are you"
> # Change the inner function to_title()
> rm(list=ls("imports"), pos="imports")
> unloadNamespace("import")
> import::from(module_recursive_outer_here.R, print_title_text)
> print_title_text("hi friend, how are you")
[1] "HI FRIEND, HOW ARE YOU"

This uses some modules already in the test code.

What it does is delete all the visible objects from the imports environment to force reloading the outer package, and then unloads the import namespace (note the difference in the ending), so that it doesn't find the names of the inner function when it goes looking.

Note, this is definitely not part of the specified behavior or the "package contract". It will probably not break any time soon, but don't rely on it in production code (should be OK though, because this is inherently a workaround to simplify development, not final code).

Hope it helps!

vdanglse commented 1 year ago

Thank you, that is very helpful.

torfason commented 1 year ago

The v.1.3.1 release is now out, and I'm doing a bit of a cleanup in the issues.

Since using import to update utility modules dynamically within a living R session is not within the scope of import, solving the recursive issue is hard, and it seems there is an acceptable workaround, I'm closing this issue now. If other matters related to this arise, or if someone is able to contribute a feature to implement this well, feel free to reopen.