Skip to content

Add bit64 support #590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Merge branch 'main' into master
  • Loading branch information
kdkavanagh authored Jun 6, 2023
commit 4a66b3ad9a3fe2cd9ffe02cc73a2c0576571a2d6
5 changes: 3 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ Imports:
methods,
png,
rappdirs,
Rcpp (>= 0.12.7),
bit64,
utils
utils,
rlang,
withr
Suggests:
callr,
knitr,
Expand Down
252 changes: 215 additions & 37 deletions src/python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,7 @@ SEXP py_to_r(PyObject* x, bool convert) {

}


/* stretchy list, modified from R sources
CAR of the list points to the last cons-cell
CDR points to the first.
Expand All @@ -1443,22 +1444,15 @@ void GrowList(SEXP args_list, SEXP tag, SEXP dflt) {
SEXP tmp = PROTECT(Rf_cons(dflt, R_NilValue));
SET_TAG(tmp, tag);


SETCDR(CAR(args_list), tmp); // set cdr on the last cons-cell
SETCAR(args_list, tmp); // update pointer to last cons cell
UNPROTECT(2);
}

// [[Rcpp::export]]
SEXP py_get_formals(PyObjectRef func) {

PyObjectPtr inspect(py_import("inspect"));
if (inspect.is_null()) stop(py_fetch_error());
PyObjectPtr get_signature(PyObject_GetAttrString(inspect.get(), "signature"));
if (get_signature.is_null()) stop(py_fetch_error());
PyObjectPtr signature(PyObject_CallFunctionObjArgs(get_signature.get(), func.get(), NULL));
if (signature.is_null()) stop(py_fetch_error());
PyObjectPtr param_dict(PyObject_GetAttrString(signature.get(), "parameters"));
if (param_dict.is_null()) stop(py_fetch_error());
SEXP py_get_formals(PyObjectRef callable)
{

static PyObject *inspect_module = NULL;
static PyObject *inspect_signature = NULL;
Expand Down Expand Up @@ -1493,10 +1487,83 @@ SEXP py_get_formals(PyObjectRef func) {
if (!inspect_Parameter_empty) throw PythonException(py_fetch_error());
}

return formals;
PyObjectPtr sig(PyObject_CallFunctionObjArgs(inspect_signature, callable.get(), NULL));
if (sig.is_null())
{
// inspect.signature() can error on builtins in cpython,
// or python functions built in C from modules
// fallback to returning formals of `...`.
PyErr_Clear();
SEXP out = Rf_cons(R_MissingArg, R_NilValue);
SET_TAG(out, Rf_install("..."));
return out;
}

PyObjectPtr parameters(PyObject_GetAttrString(sig, "parameters"));
if (parameters.is_null()) throw PythonException(py_fetch_error());

PyObjectPtr items_method(PyObject_GetAttrString(parameters, "items"));
if (items_method.is_null()) throw PythonException(py_fetch_error());

PyObjectPtr parameters_items(PyObject_CallFunctionObjArgs(items_method, NULL));
if (parameters_items.is_null()) throw PythonException(py_fetch_error());

PyObjectPtr parameters_iterator(PyObject_GetIter(parameters_items));
if (parameters_iterator.is_null()) throw PythonException(py_fetch_error());

RObject r_args(NewList());
PyObject *item;
bool has_dots = false;

while ((item = PyIter_Next(parameters_iterator))) // new ref
{
PyObjectPtr item_(item); // auto-decref
PyObject *name = PyTuple_GetItem(item, 0); // borrowed reference
PyObject *param = PyTuple_GetItem(item, 1); // borrowed reference

PyObjectPtr kind_(PyObject_GetAttrString(param, "kind")); // new ref
if (kind_.is_null()) throw PythonException(py_fetch_error());
PyObject *kind = kind_.get();

if (kind == inspect_Parameter_VAR_KEYWORD ||
kind == inspect_Parameter_VAR_POSITIONAL)
{
if (!has_dots)
{
GrowList(r_args, Rf_install("..."), R_MissingArg);
has_dots = true;
}
continue;
}

if (!has_dots && kind == inspect_Parameter_KEYWORD_ONLY)
{
GrowList(r_args, Rf_install("..."), R_MissingArg);
has_dots = true;
}

SEXP arg_default = R_MissingArg;
PyObjectPtr param_default(PyObject_GetAttrString(param, "default")); // new ref
if (param_default.is_null())
throw PythonException(py_fetch_error());

if (param_default.get() != inspect_Parameter_empty)
arg_default = py_to_r(param_default, true);

const char *name_char = PyUnicode_AsUTF8(name);
if (name_char == NULL) throw PythonException(py_fetch_error());

SEXP name_sym = Rf_installTrChar(Rf_mkCharCE(name_char, CE_UTF8));
GrowList(r_args, name_sym, arg_default);
}

if (PyErr_Occurred())
throw PythonException(py_fetch_error());

return CDR(r_args);
}


bool is_convertible_to_numpy(RObject x) {

if (!haveNumPy())
Expand Down Expand Up @@ -1582,7 +1649,7 @@ PyObject* r_to_py_numpy(RObject x, bool convert) {

// check for error
if (array == NULL)
stop(py_fetch_error());
throw PythonException(py_fetch_error());

// if this is a character vector we need to convert and set the elements,
// otherwise the memory is shared with the underlying R vector
Expand All @@ -1597,7 +1664,7 @@ PyObject* r_to_py_numpy(RObject x, bool convert) {
} else {
// wrap the R object in a capsule that's tied to the lifetime of the matrix
// (so the R doesn't deallocate the memory while python is still pointing to it)
PyObjectPtr capsule(r_object_capsule(x));
PyObjectPtr capsule(py_capsule_new(x));

// set base object using correct version of the API (detach since this
// effectively steals a reference to the provided base object)
Expand Down Expand Up @@ -1747,22 +1814,10 @@ PyObject* r_to_py_cpp(RObject x, bool convert) {
}
int res = PyList_SetItem(list, i, obj);
if (res != 0)
stop(py_fetch_error());
throw PythonException(py_fetch_error());
}
return list.detach();
}

PyObjectPtr list(PyList_New(LENGTH(sexp)));
for (R_xlen_t i = 0; i<LENGTH(sexp); i++) {
double value = REAL(sexp)[i];
// NOTE: reference to added value is "stolen" by the list
int res = PyList_SetItem(list, i, PyFloat_FromDouble(value));
if (res != 0)
throw PythonException(py_fetch_error());
}

return list.detach();

}

// complex (pass length 1 vectors as scalars, otherwise pass list)
Expand Down Expand Up @@ -2539,14 +2594,14 @@ PyObjectRef py_get_common(PyObject* object,
if (object != NULL)
return py_ref(object, convert);

// if we're silent, return Py_None
// if we're silent, return new reference to Py_None
if (silent) {
Py_IncRef(Py_None);
return py_ref(Py_None, convert);
}

// otherwise, throw an R error
stop(py_fetch_error());
throw PythonException(py_fetch_error());

}

Expand Down Expand Up @@ -2749,6 +2804,10 @@ SEXP py_dict_get_item(PyObjectRef dict, RObject key) {
return py_ref(Py_None, false);
}


Py_IncRef(item);
return py_ref(item, dict.convert());

}

// [[Rcpp::export]]
Expand Down Expand Up @@ -2808,8 +2867,9 @@ CharacterVector py_dict_get_keys_as_str(PyObjectRef dict) {
std::vector<std::string> keys;

PyObjectPtr it(PyObject_GetIter(py_keys));
if (it == NULL)
stop(py_fetch_error());

if (it.is_null())
throw PythonException(py_fetch_error());

for (PyObject* item = PyIter_Next(it);
item != NULL;
Expand All @@ -2827,14 +2887,14 @@ CharacterVector py_dict_get_keys_as_str(PyObjectRef dict) {
// if we don't have a python string, try to create one
PyObjectPtr str(PyObject_Str(item));
if (str.is_null())
stop(py_fetch_error());
throw PythonException(py_fetch_error());

keys.push_back(as_utf8_r_string(str));

}

if (PyErr_Occurred())
stop(py_fetch_error());
throw PythonException(py_fetch_error());

return CharacterVector(keys.begin(), keys.end());

Expand Down Expand Up @@ -3081,7 +3141,7 @@ SEXP py_eval_impl(const std::string& code, bool convert = true) {


if (compiledCode.is_null())
stop(py_fetch_error());
throw PythonException(py_fetch_error());

// execute the code
PyObject* main = PyImport_AddModule("__main__");
Expand Down Expand Up @@ -3124,7 +3184,7 @@ SEXP py_convert_pandas_series(PyObjectRef series) {

// get "ordered" attribute
PyObjectPtr ordered(PyObject_GetAttrString(dtype, "ordered"));
//RObject ordered = py_to_r(ordered_, true);


// populate integer vector to hold factor values
// note that we need to convert 0->1 indexing, and handle NAs
Expand All @@ -3142,9 +3202,11 @@ SEXP py_convert_pandas_series(PyObjectRef series) {
CharacterVector factor_levels(R_levels);
factor_levels.attr("dim") = R_NilValue;

factor.attr("class") = "factor";
factor.attr("levels") = factor_levels;
if (PyObject_IsTrue(ordered)) factor.attr("ordered") = true;
if (PyObject_IsTrue(ordered))
factor.attr("class") = CharacterVector({"ordered", "factor"});
else
factor.attr("class") = "factor";

R_obj = factor;

Expand Down Expand Up @@ -3259,6 +3321,11 @@ PyObjectRef r_convert_dataframe(RObject dataframe, bool convert) {
{
RObject column = VECTOR_ELT(dataframe, i);

// ensure name is converted to appropriate encoding
const char* name = is_python3()
? Rf_translateCharUTF8(names[i])
: Rf_translateChar(names[i]);

int status = 0;
if (OBJECT(column) == 0) {
if (is_convertible_to_numpy(column)) {
Expand Down Expand Up @@ -3294,7 +3361,7 @@ PyObject* r_convert_date_impl(PyObject* datetime,
static_cast<int>(date.getDay())));

if (py_date == NULL)
stop(py_fetch_error());
throw PythonException(py_fetch_error());

return py_date.detach();
}
Expand Down Expand Up @@ -3324,3 +3391,114 @@ PyObjectRef r_convert_date(DateVector dates, bool convert) {
return py_ref(list.detach(), convert);

}

// [[Rcpp::export]]
void py_set_interrupt_impl() {
PyErr_SetInterrupt();
}

// [[Rcpp::export]]
SEXP py_list_length(PyObjectRef x) {
Py_ssize_t value = PyList_Size(x);
if (value <= static_cast<Py_ssize_t>(INT_MAX))
return Rf_ScalarInteger((int) value);
else
return Rf_ScalarReal((double) value);
}

// [[Rcpp::export]]
SEXP py_len_impl(PyObjectRef x, SEXP defaultValue = R_NilValue) {

PyObject *er_type, *er_value, *er_traceback;
if (defaultValue != R_NilValue)
PyErr_Fetch(&er_type, &er_value, &er_traceback);

Py_ssize_t value = PyObject_Size(x);
if (value == -1) {
// object is missing a `__len__` method, or a `__len__` method that
// intentionally raises an Exception
if (defaultValue == R_NilValue) {
throw PythonException(py_fetch_error());
} else {
PyErr_Restore(er_type, er_value, er_traceback);
return defaultValue;
}
}

if (value <= static_cast<Py_ssize_t>(INT_MAX))
return Rf_ScalarInteger((int) value);
else
return Rf_ScalarReal((double) value);
}

// [[Rcpp::export]]
SEXP py_bool_impl(PyObjectRef x) {

// evaluate Python `not not x`
int result = PyObject_IsTrue(x);

if (result == -1) {
// Should only happen if the object has a `__bool__` method that
// intentionally throws an exception.
throw PythonException(py_fetch_error());
}

return Rf_ScalarLogical(result);
}


// [[Rcpp::export]]
SEXP py_has_method(PyObjectRef object, const std::string& name) {

if (py_is_null_xptr(object))
return Rf_ScalarLogical(false);

if (!PyObject_HasAttrString(object, name.c_str()))
return Rf_ScalarLogical(false);

PyObjectPtr attr(PyObject_GetAttrString(object, name.c_str()));
int result = PyMethod_Check(attr);

return Rf_ScalarLogical(result);
}


//' Unique identifer for Python object
//'
//' Get a globally unique identifier for a Python object.
//'
//' @note In the current implementation of CPython this is the
//' memory address of the object.
//'
//' @param object Python object
//'
//' @return Unique identifer (as string) or `NULL`
//'
//' @export
// [[Rcpp::export]]
SEXP py_id(PyObjectRef object) {
if (py_is_null_xptr(object))
return R_NilValue;

std::stringstream id;
id << (uintptr_t) object.get();

return CharacterVector({id.str()});
}

void ensure_python_initialized() {
if (s_is_python_initialized)
return;

Function initialize = Environment::namespace_env("reticulate")["ensure_python_initialized"];
initialize();
}

// [[Rcpp::export]]
PyObjectRef py_capsule(SEXP x) {
if(!s_is_python_initialized)
ensure_python_initialized();

return py_ref(py_capsule_new(x), false);

}
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.