[EDIT: The thread title and content has been changed a bit to address the more general question of dynamically building namespaces at package load-time.]
I'm trying to make a (very) lightweight package that wraps some of my existing Python code using reticulate. This Python package contains a module (the_module
) that itself is an ever-growing container for functions f1()
, f2()
,etc.
Version 1: the_package::the_module$f1()
One straight-forward way to do this is:
- Bundle the Python code with the R package (at
inst/python/<the_py_package>
). - Load the Python module within the R package with
the_module <- NULL
.onLoad <- function(libname, pkgname) {
the_module <<- reticulate::import_from_path("the_py_module", system.file("python", "the_py_package", package = packageName(), mustWork = TRUE))
}
- And include
export(the_module)
via theNAMESPACE
file.
This gives R access to the Python functions via:
the_package::the_module$f1()
the_package::the_module$f2()
## ... and so on
Version 2: Declare and export each function explicitly
As nice as Version 1 is, I'd like to have each Python function be a 'top-level' export within the package's namespace and accessible like so:
the_package::f1()
One way to achieve this is to explicitly 'declare' each function, then 'define' those functions with .onLoad
:
## for NAMESPACE exporting
f1 <- NULL
f2 <- NULL
## actually _define_ the functions at package load-time
.onLoad <- function(libname, pkgname) {
the_module <- reticulate::import_from_path("the_py_module", system.file("python", "the_py_package", package = packageName()))
f1 <<- the_module$f1
f2 <<- the_module$f2
}
... and then include export(f1)
, export(f2
), etc. via the NAMESPACE
file.
This works! But ... every time new functions are added to the Python module, these explicit declarations and definitions are required to expose those functions to R.
Version 3: Build the package namespace during .onLoad()
To reduce this extra leg-work, I've adopted this (working!) pattern, where the entire R package is 'defined' with only this .onLoad()
code:
.onLoad <- function(libname, pkgname) {
pkg_ns_env <- parent.env(environment())
the_module <- reticulate::import_from_path("the_py_module", system.file("python", "the_py_package", package = packageName(), mustWork = TRUE))
lapply(names(the_module), function(name) assign(name, the_module[[name]], pkg_ns_env))
}
... along with only this in the NAMESPACE
file:
exportPattern("^[^\\.]")
This also works! After installing the package, I can access any of the module's functions with:
the_package::f1()
the_package::f2()
## etc.
Better yet, if I make a change to the Python module and add a function f3()
, it becomes visible to R (at the next load-time) without any changes to the R package.
Question(s):
Adding functions to the package namespace during .onLoad()
seems ... non-standard.
Is it risky?
Are there caveats that I'm overlooking here?
Alternatively, what other ways (if any) have folks adopted for dynamically exporting functions from wrapper packages?