Source code for omegaml.backends.rsystem.rscripts

  1from os.path import basename, dirname
  2
  3import base64
  4import json
  5import os
  6from shutil import make_archive, unpack_archive
  7from subprocess import run
  8
  9from omegaml.backends.basedata import BaseDataBackend
 10from omegaml.backends.package.packager import RunnablePackageMixin
 11from omegaml.runtimes.rsystem import rhelper
 12from omegaml.util import tryOr
 13
 14
[docs] 15class RPackageData(RunnablePackageMixin, BaseDataBackend): 16 """ 17 Backend to support R packages 18 19 Usage:: 20 21 om.scripts.put('R://path/to/app.R', 'myname') 22 om.scripts.get('myname') 23 24 See Also: 25 * Rscript 26 * https://mastering-shiny.org/scaling-packaging.html (CC BY-NC-ND 4.0) 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).save()
64
[docs] 65 def get(self, name, localpath=None, keep=False, install=True, **kwargs): 66 """ 67 Load package from store, install it locally and load. 68 69 This copies the package's .tar.gz file from om.scripts to a local temp 70 path and runs `pip install` on it. 71 72 :param name: the name of the package 73 :param keep: keep the packages load path in sys.path, defaults to False 74 :param localpath: the local path to store the package 75 :param install: if True call pip install on the retrieved package 76 :param kwargs: 77 :return: the loaded module (RScript) 78 """ 79 pkgname = basename(name) 80 dstdir = localpath or self.data_store.tmppath 81 packagefname = '{}.tar.gz'.format(os.path.join(localpath or self.packages_path, pkgname)) 82 os.makedirs(dirname(packagefname), exist_ok=True) 83 meta = self.data_store.metadata(name) 84 outf = meta.gridfile 85 with open(packagefname, 'wb') as pkgf: 86 pkgf.write(outf.read()) 87 if install: 88 unpack_archive(packagefname, dstdir) 89 mod = RScript(dstdir) 90 else: 91 mod = os.path.join(dstdir, pkgname) 92 return mod
93 94 @property 95 def packages_path(self): 96 return os.path.join(self.data_store.tmppath, 'packages')
97 98
[docs] 99class RScript: 100 """ a Python proxy to the R process that runs a script 101 102 This provides the ``mod.run()`` interface for scripts so that 103 we can use the same semantics for R and python scripts. 104 """ 105 def __init__(self, appdir): 106 self.appdir = appdir 107
[docs] 108 def run(self, om, **kwargs): 109 """ run the script in R session 110 111 Usage: 112 The script must exist as ``{self.appdir}/app.R``. It must implement 113 the ``omega_run()`` function. 114 115 Example:: 116 117 # app.R 118 library(jsonlite) 119 omega_run <- function(om, kwargs) { 120 # if om was passed as "0", this means we're in a local mode, i.e. must import omegaml 121 om <- if (om == "0") import("omegaml") else om 122 s <- fromJSON(rawToChar(base64_dec(kwargs))) 123 s$message <- "hello from R" 124 return(toJSON(s)) 125 } 126 127 Notes: 128 - If an R session is active, will run the script by calling the script's omega_run function 129 - If no R session is active, will use RScript to source the script and run omega_run function 130 - Expects the output to be in JSON format 131 """ 132 r = rhelper() 133 if r is None: 134 r_kwargs = base64.b64encode(json.dumps(kwargs).encode('utf8')).decode('ascii') 135 rcmd = fr'Rscript -e source("{self.appdir}/app.R") -e omega_run(0,"{r_kwargs}")' 136 output = run(rcmd.split(' '), capture_output=True) 137 output = output.stdout 138 else: 139 r.source(f'{self.appdir}/app.R') 140 output = r.omega_run(om, kwargs) 141 return tryOr(lambda: json.loads(output), output)