potions is a package for easily storing and retrievinginformation viaoptions(). It therefore providesfunctionality somewhat similar to{settings},but with syntax based more closely on{here}.The intended use ofpotions is for adding novel informationtooptions() for use within single packages orworkflows.
potions has three basic functions:
brew() to store datapour() to retrieve datadrain() to clear dataThe first step is to store data usingbrew(), whichaccepts data in three formats:
brew(x = 1)list, e.g. brew(list(x = 1))brew(file = "my-config-file.yml")Information stored usingbrew can be retrieved usingpour:
Becausepotions uses a novelS3 object forall data storage, itnever overwrites existing globaloptions, and is therefore safe to use without affectingexisting workflows. For example,print.default takes it’sdefaultdigits argument fromgetOption("digits"):
If we usepotions to setdigits, we do notaffect this behaviour. Instead, the user must specifically retrieve datausingpour for these settings to be applied:
library(potions)brew(digits =3)print(pi,digits =pour("digits"))# using potions#> [1] 3.14print(pi)# default is unaffected#> [1] 3.141593This feature - i.e. storing data in a novelS3 object -means thatpotions can distinguish between interactive usein the console versus being called within a package. Data can beprovided and used independently by multiple packages, and in theconsole, without generating conflicts.
Options stored usingpotions are not persistent acrosssessions; you will need to reload options each time you open a newworkspace. It is unlikely, therefore, that you will need to ‘clear’ thedata stored bypotions at any point. If you do need toremove data, you can do so usingdrain() (without anyfurther arguments).
config filesOften it is necessary to share a script, but without sharing certainsensitive information necessary to run the code. A common example is APIkeys or other sensitive information required to download data from a webservice. In such cases, the default, interactive method of usingbrew() is insufficient, i.e.
To avoid this problem, you can instead supply the path to a filecontaining that information, i.e.
You can then simply add the corresponding file name to yourgitignore, and your script will still run, without sharingsensitive information.
potions in package developmentWhen weighing up architectural decisions about how packages shouldshare information between functions, there are a few solutions thatdevelopers can choose between:
sysdata.rda, which supports internal use of named objectswhile avoidingoptions() completely.options(), and for which there is no override, it ispossible to temporarily resetoptions() within a function.In these cases, CRAN requires that the initial state be restored afteruse, for whichon.exit() is a sensible choice (SeeAdvanced R section6.7.4).potions orsettings can bevaluable.To usepotions in a package development situation,create a file in theR directory calledonLoad.R, containing the following code:
.onLoad<-function(libname, pkgname) {if(pkgname=="packagenamehere") { potions::brew(.pkg ="packagenamehere") }}This is important because it tellspotions that you aredeveloping a package, what that package is called, and where futurecalls tobrew() from within that package should place theirdata. It is also possible to add defaults here, e.g.
.onLoad<-function(libname, pkgname) {if(pkgname=="packagenamehere") { potions::brew( n_attempts==5, verbose==TRUE,.pkg ="packagenamehere") }}Often when developing a package, you will want users to call your ownconfiguration function, rather than callbrew() directly.This provides greater control over the names & types of data storedbypotions, which in turn gives you - the developer -greater certainty when calling those datawithin your packageviapour(). For example, you might want to specify that aspecific argument is supplied as numeric:
packagename_config<-function(fontsize =10){if(!is.numeric(fontsize)){ rlang::abort("Argument `fontsize` must be a number") }brew(list(fontsize = fontsize))}An additional benefit of writing a wrapper function is to allow usersto provide their ownconfig file. The easiest way to dothis is to support afile argument within your ownfunction, then pass this directly tobrew():
This approach is risky, however, as it doesn’t allow any checks. Analternative is to intercept the file, run your own checks, then pass theresult tobrew():