1import enum 2import os 3import sys 4import typing 5import warnings 6from rpy2.rinterface_lib import openrlib 7from rpy2.rinterface_lib import callbacks 8 9ffi = openrlib.ffi 10 11_options = ('rpy2', '--quiet', '--no-save') # type: typing.Tuple[str, ...] 12_DEFAULT_C_STACK_LIMIT = -1 13rpy2_embeddedR_isinitialized = 0x00 14rstart = None 15 16 17# TODO: move initialization-related code to _rinterface ? 18class RPY_R_Status(enum.Enum): 19 """Possible status for the embedded R.""" 20 INITIALIZED = 0x01 21 BUSY = 0x02 22 ENDED = 0x04 23 24 25def set_initoptions(options: typing.Tuple[str]) -> None: 26 """Set initialization options for the embedded R. 27 28 :param:`options` A tuple of string with the options 29 (e.g., '--verbose', '--quiet'). 30 """ 31 if rpy2_embeddedR_isinitialized: 32 raise RuntimeError('Options can no longer be set once ' 33 'R is initialized.') 34 global _options 35 for x in options: 36 assert isinstance(x, str) 37 _options = tuple(options) 38 39 40def get_initoptions() -> typing.Tuple[str, ...]: 41 """Get the initialization options for the embedded R.""" 42 return _options 43 44 45def isinitialized() -> bool: 46 """Is the embedded R initialized.""" 47 return bool(rpy2_embeddedR_isinitialized & RPY_R_Status.INITIALIZED.value) 48 49 50def _setinitialized() -> None: 51 """Set the embedded R as initialized. 52 53 This may result in a later segfault if used with the embedded R has not 54 been initialized. You should not have to use it.""" 55 global rpy2_embeddedR_isinitialized 56 rpy2_embeddedR_isinitialized = RPY_R_Status.INITIALIZED.value 57 58 59def isready() -> bool: 60 """Is the embedded R ready for use.""" 61 INITIALIZED = RPY_R_Status.INITIALIZED 62 return bool( 63 rpy2_embeddedR_isinitialized == INITIALIZED.value 64 ) 65 66 67def assert_isready() -> None: 68 """Assert whether R is ready (initialized). 69 70 Raises an RNotReadyError if it is not.""" 71 if not isready(): 72 raise RNotReadyError( 73 'The embedded R is not ready to use.') 74 75 76class RNotReadyError(Exception): 77 """Embedded R is not ready to use.""" 78 pass 79 80 81class RRuntimeError(Exception): 82 """Error generated by R.""" 83 pass 84 85 86def _setcallback(rlib, rlib_symbol: str, 87 callbacks, 88 callback_symbol: str) -> None: 89 """Set R callbacks.""" 90 if callback_symbol is None: 91 new_callback = ffi.NULL 92 else: 93 new_callback = getattr(callbacks, callback_symbol) 94 setattr(rlib, rlib_symbol, new_callback) 95 96 97CALLBACK_INIT_PAIRS = (('ptr_R_WriteConsoleEx', '_consolewrite_ex'), 98 ('ptr_R_WriteConsole', None), 99 ('ptr_R_ShowMessage', '_showmessage'), 100 ('ptr_R_ReadConsole', '_consoleread'), 101 ('ptr_R_FlushConsole', '_consoleflush'), 102 ('ptr_R_ResetConsole', '_consolereset'), 103 ('ptr_R_ChooseFile', '_choosefile'), 104 ('ptr_R_ShowFiles', '_showfiles'), 105 ('ptr_R_CleanUp', '_cleanup'), 106 ('ptr_R_ProcessEvents', '_processevents'), 107 ('ptr_R_Busy', '_busy')) 108 109 110# TODO: can init_once() be used here ? 111def _initr( 112 interactive: bool = True, 113 _want_setcallbacks: bool = True, 114 _c_stack_limit: int = _DEFAULT_C_STACK_LIMIT 115) -> typing.Optional[int]: 116 117 rlib = openrlib.rlib 118 ffi_proxy = openrlib.ffi_proxy 119 if ( 120 ffi_proxy.get_ffi_mode(openrlib._rinterface_cffi) 121 == 122 ffi_proxy.InterfaceType.ABI 123 ): 124 callback_funcs = callbacks 125 else: 126 callback_funcs = rlib 127 128 with openrlib.rlock: 129 if isinitialized(): 130 return None 131 elif openrlib.R_HOME is None: 132 raise ValueError('openrlib.R_HOME cannot be None.') 133 elif openrlib.rlib.R_NilValue != ffi.NULL: 134 warnings.warn( 135 'R was initialized outside of rpy2 (R_NilValue != NULL). ' 136 'Trying to use it nevertheless.' 137 ) 138 _setinitialized() 139 return None 140 os.environ['R_HOME'] = openrlib.R_HOME 141 options_c = [ffi.new('char[]', o.encode('ASCII')) for o in _options] 142 n_options = len(options_c) 143 n_options_c = ffi.cast('int', n_options) 144 145 # TODO: Conditional in C code 146 rlib.R_SignalHandlers = 0 147 148 # Instead of calling Rf_initEmbeddedR which breaks threaded context 149 # perform the initialization manually to set R_CStackLimit before 150 # calling setup_Rmainloop(), see: 151 # https://github.com/rpy2/rpy2/issues/729 152 rlib.Rf_initialize_R(n_options_c, options_c) 153 if _c_stack_limit: 154 rlib.R_CStackLimit = ffi.cast('uintptr_t', _c_stack_limit) 155 rlib.R_Interactive = True 156 rlib.setup_Rmainloop() 157 158 _setinitialized() 159 160 rlib.R_Interactive = interactive 161 162 # TODO: Conditional definition in C code 163 # (Aqua, TERM, and TERM not "dumb") 164 rlib.R_Outputfile = ffi.NULL 165 rlib.R_Consolefile = ffi.NULL 166 167 if _want_setcallbacks: 168 for rlib_symbol, callback_symbol in CALLBACK_INIT_PAIRS: 169 _setcallback(rlib, rlib_symbol, 170 callback_funcs, callback_symbol) 171 172 return 1 173 174 175def endr(fatal: int) -> None: 176 global rpy2_embeddedR_isinitialized 177 rlib = openrlib.rlib 178 with openrlib.rlock: 179 if rpy2_embeddedR_isinitialized & RPY_R_Status.ENDED.value: 180 return 181 rlib.R_dot_Last() 182 rlib.R_RunExitFinalizers() 183 rlib.Rf_KillAllDevices() 184 rlib.R_CleanTempDir() 185 rlib.R_gc() 186 rlib.Rf_endEmbeddedR(fatal) 187 rpy2_embeddedR_isinitialized ^= RPY_R_Status.ENDED.value 188 189 190_REFERENCE_TO_R_SESSIONS = 'https://github.com/rstudio/reticulate/issues/98' 191_R_SESSION_INITIALIZED = 'R_SESSION_INITIALIZED' 192_PYTHON_SESSION_INITIALIZED = 'PYTHON_SESSION_INITIALIZED' 193 194 195def get_r_session_status(r_session_init=None) -> dict: 196 """Return information about the R session, if available. 197 198 Information about the R session being already initialized can be 199 communicated by an environment variable exported by the process that 200 initialized it. See discussion at: 201 %s 202 """ % _REFERENCE_TO_R_SESSIONS 203 204 res = {'current_pid': os.getpid()} 205 206 if r_session_init is None: 207 r_session_init = os.environ.get(_R_SESSION_INITIALIZED) 208 if r_session_init: 209 for item in r_session_init.split(':'): 210 try: 211 key, value = item.split('=', 1) 212 except ValueError: 213 warnings.warn( 214 'The item %s in %s should be of the form key=value.' % 215 (item, _R_SESSION_INITIALIZED) 216 ) 217 res[key] = value 218 return res 219 220 221def is_r_externally_initialized() -> bool: 222 r_status = get_r_session_status() 223 return str(r_status['current_pid']) == str(r_status.get('PID')) 224 225 226def set_python_process_info() -> None: 227 """Set information about the Python process in an environment variable. 228 229 See discussion at: 230 %s 231 """ % _REFERENCE_TO_R_SESSIONS 232 233 info = (('current_pid', os.getpid()), 234 ('sys.executable', sys.executable)) 235 info_string = ':'.join('%s=%s' % x for x in info) 236 os.environ[_PYTHON_SESSION_INITIALIZED] = info_string 237