Source code for omegaml.backends.rsystem.rscripts

  1import base64
  2import json
  3import os
  4from os.path import basename, dirname
  5from shutil import make_archive, unpack_archive
  6from subprocess import run
  7
  8from omegaml.backends.basedata import BaseDataBackend
  9from omegaml.backends.package.packager import RunnablePackageMixin
 10from omegaml.runtimes.rsystem import rhelper
 11from omegaml.util import tryOr
 12
 13
[docs] 14class RPackageData(RunnablePackageMixin, BaseDataBackend): 15 """ 16 Backend to support R packages 17 18 Usage:: 19 20 om.scripts.put('R://path/to/app.R', 'myname') 21 om.scripts.get('myname') 22 23 See Also: 24 * Rscript 25 * https://mastering-shiny.org/scaling-packaging.html (CC BY-NC-ND 4.0) 26 """ 27 28 KIND = 'package.r' 29
[docs] 30 @classmethod 31 def supports(self, obj, name, **kwargs): 32 return isinstance(obj, str) and obj.startswith('R://')
33
[docs] 34 def put(self, obj, name, attributes=None, **kwargs): 35 """ 36 save a R package 37 38 This takes the full path to a setuptools setup.py, or a directory 39 containing a setup.py file. It then executes `python setup.py sdist` 40 and stores the resulting .tar.gz file in om.scripts 41 42 :param obj: full path to package file or directory, syntax as 43 pkg://path/to/dist.tar.gz or pkg://path/to/setup.py 44 :param name: name of package. must be the actual name of the package 45 as specified in setup.py 46 :return: the Metadata object 47 """ 48 pkgsrc = pkgdist = obj.split('//')[1] 49 pkgsrc = pkgsrc.replace('app.R', '') 50 if not 'tar.gz' in os.path.basename(pkgdist): 51 distdir = os.path.join(pkgsrc, 'dist') 52 os.makedirs(distdir, exist_ok=True) 53 base_name = os.path.join(distdir, f'{name}') 54 pkgdist = make_archive(base_name, 'gztar', pkgsrc) 55 filename = self.data_store.object_store_key(name, 'pkg', hashed=True) 56 gridfile = self._store_to_file(self.data_store, pkgdist, filename) 57 return self.data_store._make_metadata( 58 name=name, 59 prefix=self.data_store.prefix, 60 bucket=self.data_store.bucket, 61 kind=RPackageData.KIND, 62 attributes=attributes, 63 gridfile=gridfile, 64 ).save()
65
[docs] 66 def get(self, name, localpath=None, keep=False, install=True, **kwargs): 67 """ 68 Load package from store, install it locally and load. 69 70 This copies the package's .tar.gz file from om.scripts to a local temp 71 path and runs `pip install` on it. 72 73 :param name: the name of the package 74 :param keep: keep the packages load path in sys.path, defaults to False 75 :param localpath: the local path to store the package 76 :param install: if True call pip install on the retrieved package 77 :param kwargs: 78 :return: the loaded module (RScript) 79 """ 80 pkgname = basename(name) 81 dstdir = localpath or self.data_store.tmppath 82 packagefname = f'{os.path.join(localpath or self.packages_path, pkgname)}.tar.gz' 83 os.makedirs(dirname(packagefname), exist_ok=True) 84 meta = self.data_store.metadata(name) 85 outf = meta.gridfile 86 with open(packagefname, 'wb') as pkgf: 87 pkgf.write(outf.read()) 88 if install: 89 unpack_archive(packagefname, dstdir) 90 mod = RScript(dstdir) 91 else: 92 mod = os.path.join(dstdir, pkgname) 93 return mod
94 95 @property 96 def packages_path(self): 97 return os.path.join(self.data_store.tmppath, 'packages')
98 99
[docs] 100class RScript: 101 """a Python proxy to the R process that runs a script 102 103 This provides the ``mod.run()`` interface for scripts so that 104 we can use the same semantics for R and python scripts. 105 """ 106 107 def __init__(self, appdir): 108 self.appdir = appdir 109
[docs] 110 def run(self, om, **kwargs): 111 """run the script in R session 112 113 Usage: 114 The script must exist as ``{self.appdir}/app.R``. It must implement 115 the ``omega_run()`` function. 116 117 Example:: 118 119 # app.R 120 library(jsonlite) 121 omega_run <- function(om, kwargs) { 122 # if om was passed as "0", this means we're in a local mode, i.e. must import omegaml 123 om <- if (om == "0") import("omegaml") else om 124 s <- fromJSON(rawToChar(base64_dec(kwargs))) 125 s$message <- "hello from R" 126 return(toJSON(s)) 127 } 128 129 Notes: 130 - If an R session is active, will run the script by calling the script's omega_run function 131 - If no R session is active, will use RScript to source the script and run omega_run function 132 - Expects the output to be in JSON format 133 """ 134 r = rhelper() 135 if r is None: 136 r_kwargs = base64.b64encode(json.dumps(kwargs).encode('utf8')).decode('ascii') 137 rcmd = rf'Rscript -e source("{self.appdir}/app.R") -e omega_run(0,"{r_kwargs}")' 138 output = run(rcmd.split(' '), capture_output=True) 139 output = output.stdout 140 else: 141 r.source(f'{self.appdir}/app.R') 142 output = r.omega_run(om, kwargs) 143 return tryOr(lambda: json.loads(output), output)