1"""Mapping between Python objects, C objects, and R objects.""" 2 3# TODO: rename the module with a prefix _ to indicate that this should 4# not be used outside of rpy2's own code 5 6from typing import Callable 7from typing import Dict 8from typing import Optional 9from typing import Type 10from typing import Union 11from rpy2.rinterface_lib import openrlib 12from rpy2.rinterface_lib import _rinterface_capi as _rinterface 13 14ffi = openrlib.ffi 15 16_R_RPY2_MAP = {} # type: Dict[int, Type] 17 18 19class DummyMissingRpy2Map(object): 20 def __init__(self, *args, **kwargs): 21 raise NotImplementedError('The default object mapper class is no set.') 22 23 24_R_RPY2_DEFAULT_MAP: Type[ 25 Union[DummyMissingRpy2Map, '_rinterface.SupportsSEXP'] 26] = DummyMissingRpy2Map 27 28# TODO: shouldn't the second type strictly inherit from an rpy2 29# R object ? 30_PY_RPY2_MAP = {} # type: Dict[Type, Callable] 31 32 33def _cdata_to_rinterface(cdata): 34 scaps = _rinterface.SexpCapsule(cdata) 35 t = cdata.sxpinfo.type 36 if t in _R_RPY2_MAP: 37 cls = _R_RPY2_MAP[t] 38 else: 39 cls = _R_RPY2_DEFAULT_MAP 40 return cls(scaps) 41 42 43def _cdata_res_to_rinterface(function): 44 def _(*args, **kwargs): 45 cdata = function(*args, **kwargs) 46 # TODO: test cdata is of the expected CType 47 return _cdata_to_rinterface(cdata) 48 return _ 49 50 51def _sexpcapsule_to_rinterface(scaps: '_rinterface.SexpCapsule'): 52 cls = _R_RPY2_MAP.get(scaps.typeof, _R_RPY2_DEFAULT_MAP) 53 return cls(scaps) 54 55 56# TODO: The name of the function is misleading, I think. Consider changing it. 57def _python_to_cdata(obj): 58 t = type(obj) 59 if t in _PY_RPY2_MAP: 60 cls = _PY_RPY2_MAP[t] 61 else: 62 raise ValueError(obj) 63 # cls = _PY_RPY2_DEFAULT_MAP 64 cdata = cls(obj) 65 return cdata 66 67 68# TODO: can scalars in R's C API be used ? 69def _int_to_sexp(val: int): 70 rlib = openrlib.rlib 71 # TODO: test value is not too large for R's ints 72 s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.INTSXP, 1)) 73 openrlib.SET_INTEGER_ELT(s, 0, val) 74 rlib.Rf_unprotect(1) 75 return s 76 77 78def _bool_to_sexp(val: bool): 79 # TODO: test value is not too large for R's ints 80 rlib = openrlib.rlib 81 s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.LGLSXP, 1)) 82 openrlib.SET_LOGICAL_ELT(s, 0, int(val)) 83 rlib.Rf_unprotect(1) 84 return s 85 86 87def _float_to_sexp(val: float): 88 rlib = openrlib.rlib 89 s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.REALSXP, 1)) 90 openrlib.SET_REAL_ELT(s, 0, val) 91 rlib.Rf_unprotect(1) 92 return s 93 94 95def _complex_to_sexp(val: complex): 96 rlib = openrlib.rlib 97 s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.CPLXSXP, 1)) 98 openrlib.SET_COMPLEX_ELT( 99 s, 0, 100 val 101 ) 102 rlib.Rf_unprotect(1) 103 return s 104 105 106# Default encoding for converting R string back to Python 107# As defined in R_API.h, possible values are 108# CE_NATIVE = 0, 109# CE_UTF8 = 1, 110# CE_LATIN1 = 2, 111# CE_BYTES = 3, 112# CE_SYMBOL = 5, 113# CE_ANY = 99 114 115# Default encoding for converting R strings to Python 116_R_ENC_PY = {None: 'ascii'} 117 118 119def _str_to_cchar(s: str, encoding: str = 'utf-8'): 120 # TODO: use isString and installTrChar 121 b = s.encode(encoding) 122 return ffi.new('char[]', b) 123 124 125def _cchar_to_str(c, encoding: str) -> str: 126 # TODO: use isString and installTrChar 127 s = ffi.string(c).decode(encoding) 128 return s 129 130 131def _cchar_to_str_with_maxlen(c, maxlen: int, encoding: str) -> str: 132 # TODO: use isString and installTrChar 133 s = ffi.string(c, maxlen).decode(encoding) 134 return s 135 136 137def _str_to_charsxp(val: Optional[str]): 138 rlib = openrlib.rlib 139 if val is None: 140 s = rlib.R_NaString 141 else: 142 cchar = _str_to_cchar(val, encoding='utf-8') 143 s = rlib.Rf_mkCharCE(cchar, openrlib.rlib.CE_UTF8) 144 return s 145 146 147def _str_to_sexp(val: str): 148 rlib = openrlib.rlib 149 s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.STRSXP, 1)) 150 charval = _str_to_charsxp(val) 151 rlib.SET_STRING_ELT(s, 0, charval) 152 rlib.Rf_unprotect(1) 153 return s 154 155 156def _str_to_symsxp(val: str): 157 rlib = openrlib.rlib 158 cchar = _str_to_cchar(val) 159 s = rlib.Rf_install(cchar) 160 return s 161 162 163_PY_R_MAP = {} # type: Dict[Type, Union[Callable, None, bool]] 164 165 166# TODO: Do special values such as NAs need to be mapped into a SEXP when 167# a scalar ? 168def _get_cdata(obj): 169 cls = _PY_R_MAP.get(type(obj)) 170 if cls is False: 171 cdata = obj 172 elif cls is None: 173 try: 174 cdata = obj.__sexp__._cdata 175 except AttributeError: 176 raise ValueError('Not an rpy2 R object and unable ' 177 'to map it into one: %s' % repr(obj)) 178 else: 179 cdata = cls(obj) 180 return cdata 181