5 Ревизии 560ec9086b ... ef292203aa

Автор SHA1 Съобщение Дата
  813gan ef292203aa Add traceback to exception message and more data to failed export message преди 1 месец
  813gan 0bf86b1192 Replace PyErr_GetRaisedException compatibility macro with function преди 1 месец
  813gan 56294dd067 Rename call_function to call_py and allow it to call methods преди 1 месец
  813gan e9cbf20366 Allow keyword arguments in emacspy--call-function преди 1 месец
  813gan 2f8dae37e5 Translate python dictionary to elisp hash преди 1 месец
променени са 4 файла, в които са добавени 106 реда и са изтрити 97 реда
  1. 17 5
      emacspy.el
  2. 23 27
      emacspy.pyx
  3. 35 51
      subinterpreter.c
  4. 31 14
      tests/test.el

+ 17 - 5
emacspy.el

@@ -14,6 +14,7 @@
 
 ;;; Code:
 
+(require 'cl-lib)
 (eval-when-compile (require 'subr-x))
 (require 'python-environment)
 
@@ -28,6 +29,13 @@
 	  (mapcar (lambda (key) (gethash key hash)) keys))
     (list keys values) ))
 
+(defun emacspy--lists-to-hash-table (keys values)
+  "Utility function that convert lists of `KEYS' and `VALUES' to hash."
+  (let ((hash (make-hash-table :test 'equal)))
+    (cl-mapcar (lambda (k v) (puthash k v hash))
+     keys values)
+    hash))
+
 (require 'emacspy-module)
 
 (defun python-environment-packages-paths (&optional root)
@@ -39,6 +47,13 @@
 
 ;; emacspy public API
 
+(defun emacspy-alist2hash (alist)
+  "Convert `ALIST' to hash for easy creation of Python dicts."
+  (let ((hash (make-hash-table) ))
+    (dolist (kv alist)
+      (puthash (car kv) (cdr kv) hash))
+    hash))
+
 (defun emacspy-python-pip-install (subinterpreter packages &optional virtualenv)
   "Install packages for `SUBINTERPRETER' from string list `PACKAGES'."
     (let* ((packages-shell-arg (mapcar 'shell-quote-argument packages)))
@@ -118,11 +133,8 @@ https://docs.python.org/3/library/sys.html#sys.base_prefix"
       (py-import subinterpreter module as)
     (py-import subinterpreter module)))
 
-(defun emacspy-call-function (subinterpreter function_name as &rest args)
-  (apply 'py-call-function subinterpreter function_name as args))
-
-(defun emacspy-call-method (subinterpreter obj_name method_name as &rest args)
-    (apply 'py-call-method subinterpreter obj_name method_name as args))
+(defun emacspy-call (subinterpreter obj_name method_name &optional as &rest args)
+    (apply 'emacspy--call subinterpreter obj_name method_name as args nil))
 
 (provide 'emacspy)
 

+ 23 - 27
emacspy.pyx

@@ -226,7 +226,7 @@ cdef class EmacsValue:
             return self.as_list()
         elif my_type == "hash-table":
             return self.as_dict()
-        raise ValueError("Unable to export emacs value")
+        raise ValueError(f"Unable to export emacs value of type '{my_type} ({str(self)})'")
 
     def __str__(self):
         return _F().prin1_to_string(self).str()
@@ -246,6 +246,8 @@ cdef emacs_value unwrap(obj) except *:
         obj = make_list(obj)
     elif isinstance(obj, tuple):
         obj = make_list(list(obj))
+    elif isinstance(obj, dict):
+        obj = make_dict(obj)
 
     if isinstance(obj, EmacsValue):
         return (<EmacsValue>obj).v
@@ -282,6 +284,16 @@ cpdef make_bool(bool b):
 cpdef make_list(list l):
     return funcall(sym("list"), l)
 
+cpdef make_dict(dict d):
+    if not d:
+        return funcall(sym("make-hash-table"), [])
+    keys = []
+    values = []
+    for k, v in d.items():
+        keys.append(k)
+        values.append(v)
+    return funcall(sym("emacspy--lists-to-hash-table"), [keys, values])
+
 cdef emacs_value string_ptr(str s):
     cdef emacs_env* env = get_env()
     s_utf8 = s.encode('utf8')
@@ -342,7 +354,7 @@ cdef emacs_value call_python_object(emacs_env *env, ptrdiff_t nargs, emacs_value
         c_result = unwrap(result)
     except BaseException as exc:
         c_result = string_ptr("error")
-        msg = type(exc).__name__ + ': ' + str(exc)
+        msg = type(exc).__name__ + ': ' + str(exc) + "\n" + traceback.format_exc()
         env.non_local_exit_signal(env, sym_ptr('python-exception'), string_ptr(msg))
 
     current_env = prev_env
@@ -365,8 +377,7 @@ cdef extern from "subinterpreter.c":
     object destroy_subinterpreter(char*)
     object list_subinterpreters()
     object run_string(char*, char*, object)
-    object call_method(char*, object, object, object, object, object)
-    object call_function(char*, object, object, object)
+    object call_py(char*, object, object, object, object, object)
     object import_module(char*, object, object)
     object get_global_variable(char*, object)
     object get_object_attr(char*, object, object, object)
@@ -424,33 +435,18 @@ def init():
             raise ret
         return ret
 
-    @defun('py-call-method')
-    def call_object_python(interpreter_name, obj_name, method_name, target_name='', *args):
-        if target_name and target_name.to_python_type():
-            target_name = target_name.to_python_type()
-        else:
-            target_name = ''
-        args_py = list((arg.to_python_type() for arg in args))
-        ret = call_method(str_elisp2c(interpreter_name), obj_name.to_python_type(), \
-                          method_name.to_python_type(), tuple(), target_name, args_py)
-        if isinstance(ret, BaseException):
-            raise ret
-        return ret
-
-    @defun('py-call-function')
-    def call_function_python(interpreter_name, function_name, target_name='', *args):
-        if target_name and target_name.to_python_type():
-            target_name = target_name.to_python_type()
-        else:
-            target_name = ''
-        args_py = tuple((arg.to_python_type() for arg in args))
-        ret = call_function(str_elisp2c(interpreter_name), function_name.to_python_type(), \
-                            target_name, args_py)
+    @defun('emacspy--call')
+    def call_function_python(interpreter_name, object_name, method_name, target_name, args, kwargs):
+        target_name_py: str = target_name.to_python_type() or ''
+        args_py: tuple = tuple(args.to_python_type() or tuple())
+        kwargs_py: dict = kwargs.to_python_type() or {}
+        ret = call_py(str_elisp2c(interpreter_name), \
+                            object_name.to_python_type(), method_name.to_python_type(), \
+                            target_name_py, args_py, kwargs_py)
         if isinstance(ret, BaseException):
             raise ret
         return ret
 
-
     @defun('py-get-global-variable')
     def get_global_var(interpreter_name, var_name):
         ret = get_global_variable(str_elisp2c(interpreter_name), var_name.to_python_type())

+ 35 - 51
subinterpreter.c

@@ -7,13 +7,12 @@
 #define MAX_INTERPRETER_NAME_LEN 100
 
 #ifdef PYTHON311OLDER
-#define PyErr_GetRaisedException()                                   \
-	({                                                           \
-		PyObject *type, *value, *traceback;                  \
-		PyErr_Fetch(&type, &value, &traceback);              \
-		PyErr_NormalizeException(&type, &value, &traceback); \
-		value;                                               \
-	})
+PyObject *PyErr_GetRaisedException() {
+	PyObject *type, *value, *traceback;
+	PyErr_Fetch(&type, &value, &traceback);
+	PyErr_NormalizeException(&type, &value, &traceback);
+	return value;
+}
 #endif
 
 #define SUBINTERPRETER_SWITCH                                                       \
@@ -173,71 +172,56 @@ finish:
 	SUBINTERPRETER_RETURN;
 }
 
-PyObject *call_method(char *interpreter_name, PyObject *obj_name, PyObject *method_name,
-    PyObject *kwnames, PyObject *target_name, PyObject *args_pylist) {
-	SUBINTERPRETER_SWITCH;
-	PyObject *global_dict = PyModule_GetDict(sub_interpreter->main_module);
-	Py_ssize_t nargs = PyList_Size(args_pylist);
-	size_t nargsf = 1 + PyList_Size(args_pylist); // TODO sign to unsign conversion??
-	size_t size_obj_args = nargsf * sizeof(PyObject);
-	PyObject **obj_with_args = malloc(size_obj_args);
-	assert(obj_with_args);
-	obj_with_args[0] = PyObject_GetItem(global_dict, obj_name); // New reference
-
-	PyObject *obj = NULL;
-
-	if (NULL == obj_with_args[0]) {
-		PyErr_SetObject(PyExc_KeyError, obj_name);
-		exception = PyErr_GetRaisedException();
-		goto finish;
-	}
-
-	for (unsigned int i = 0; i < nargs; ++i) {
-		obj_with_args[1 + i] = PyList_GetItem(args_pylist, i);
-		assert(obj_with_args[1 + i]);
-	}
-
-	obj = PyObject_VectorcallMethod(method_name, obj_with_args, nargsf, kwnames);
-	exception = PyErr_GetRaisedException();
-	if (exception)
-		goto finish;
-
-	SETUP_RET;
-finish:
-	Py_XDECREF(obj_with_args[0]);
-	free(obj_with_args);
-	SUBINTERPRETER_RETURN;
-}
-
-PyObject *call_function(char *interpreter_name, PyObject *callable_name, PyObject *target_name,
-    PyObject *args_pylist) {
+PyObject *call_py(char *interpreter_name, PyObject *obj_name, PyObject *method_name,
+    PyObject *target_name, PyObject *args_pytuple, PyObject *kwargs_pydict) {
 	SUBINTERPRETER_SWITCH;
 	PyObject *global_dict = PyModule_GetDict(sub_interpreter->main_module);
-	PyObject *callable = PyObject_GetItem(global_dict, callable_name); // New reference
+	PyObject *call_obj = PyObject_GetItem(global_dict, obj_name); // New reference
 	PyObject *obj = NULL;
+	PyObject *method = NULL;
 
-	if (NULL == callable) {
+	if (NULL == call_obj) {
 		PyErr_Clear();
 		PyObject *builtins_name = PyUnicode_FromString("__builtins__");
 		PyObject *builtins = PyObject_GetItem(global_dict, builtins_name);
 		Py_DECREF(builtins_name);
-		callable = PyObject_GetAttr(builtins, callable_name); // New reference
+		call_obj = PyObject_GetAttr(builtins, obj_name); // New reference
 	}
 
 	exception = PyErr_GetRaisedException();
 	if (exception)
 		goto finish;
 
-	assert(callable);
+	assert(call_obj);
 
-	obj = PyObject_Call(callable, args_pylist, NULL); // New reference
+	if (1 != PyObject_IsTrue(kwargs_pydict)) {
+		kwargs_pydict = NULL;
+	}
+
+	if (1 == PyObject_IsTrue(method_name)) {
+		method = PyObject_GetAttr(call_obj, method_name); // New reference
+
+		exception = PyErr_GetRaisedException();
+		if (exception)
+			goto finish;
+
+		if (0 == PyCallable_Check(method)) {
+			PyErr_SetObject(PyExc_ValueError, method_name);
+			exception = PyErr_GetRaisedException();
+			goto finish;
+		}
+		obj = PyObject_Call(method, args_pytuple, kwargs_pydict); // New reference
+	} else {
+		obj = PyObject_Call(call_obj, args_pytuple, kwargs_pydict); // New reference
+	}
 	exception = PyErr_GetRaisedException();
 	if (exception)
 		goto finish;
 
 	SETUP_RET;
 finish:
-	Py_XDECREF(callable);
+	Py_XDECREF(call_obj);
+	Py_XDECREF(method);
 	SUBINTERPRETER_RETURN;
 }
 

+ 31 - 14
tests/test.el

@@ -23,24 +23,33 @@
                    "test" (py-set-global "test" () "test_bool")))) )
 
 (ert-deftest ert-test-emacspy-py-call-method ()
-  (should (string= "/" (py-call-method "test" "ospath" "realpath" nil "/")))
-  (should (py-call-method "test" "ospath" "realpath" "call_method_test_var" "/"))
-  (should (string= "/" (py-get-global-variable "test" "call_method_test_var")))
-  (should-error (string= "/" (py-call-method "test" "ospath" "DUMMY_METHOD"))
+  (should (string= "/" (emacspy--call "test" "ospath" "realpath" nil '("/") nil)))
+  (should (emacspy--call "test" "ospath" "realpath" "call_method_test_var" '("/") nil))
+  (should (string= "/" (py-get-global-variable "test" "call_method_test_var" )))
+  (should-error (emacspy--call "test" "ospath" "DUMMY_METHOD" nil (emacspy-alist2hash nil))
                 :type 'python-exception)
-  (should-error (string= "/" (py-call-method "test" "NON_EXISTING_OBJECT" "DUMMY_METHOD"))
+  (should-error (emacspy--call "test" "NON_EXISTING_OBJECT" "DUMMY_METHOD" nil (emacspy-alist2hash nil))
+                :type 'python-exception)
+  (should-error (emacspy--call "test" "string" "digits" nil nil nil)
                 :type 'python-exception) )
 
 (ert-deftest ert-test-emacspy-py-get-global-variable ()
   (should (string= "__main__" (py-get-global-variable  "test" "__name__")))
   (should-error (py-get-global-variable  "test" "NON_EXISTING_VARIABLE")) )
 
-(ert-deftest ert-test-emacspy-py-call-function ()
-  (should (eq 3 (py-call-function "test" "len" nil "123")))
-  (should (py-call-function "test" "len" "call_function_test_var" "123"))
+(ert-deftest ert-test-emacspy-emacspy--call-function ()
+  (should (eq 3 (emacspy--call "test" "len" nil nil '("123") nil)))
+  (should (emacspy--call "test" "len" nil "call_function_test_var" '("123") nil))
   (should (eq 3 (py-get-global-variable  "test" "call_function_test_var")))
-  (should-error (py-call-function "test" "NON-EXISTING-FUNCTION" nil "123")
-                :type 'python-exception) )
+  (should-error (emacspy--call "test" "NON-EXISTING-FUNCTION" nil nil '("123") (emacspy-alist2hash nil))
+                :type 'python-exception)
+
+  (should (emacspy--call "test" "dict" nil "call_function_kvargs_test_var" nil
+                                  (emacspy-alist2hash '(("some_test" . 1) ("test" . "also_test"))) ))
+  (let ((ret (py-get-global-variable "test" "call_function_kvargs_test_var")))
+    (should (hash-table-p ret))
+    (should (eq 1 (gethash "some_test" ret )))
+    (should (string= "also_test" (gethash "test" ret))) ) )
 
 (ert-deftest ert-test-emacspy-py-get-object-attr ()
   (should (string= "0123456789" (py-get-object-attr "test" "string" "digits")))
@@ -60,10 +69,10 @@
 (ert-deftest ert-test-emacspy-import-custom-module ()
   (should (py-import "test" "sys"))
   (should (py-get-object-attr "test" "sys" "path" "syspath"))
-  (should-not (py-call-method "test" "syspath" "append" nil "./tests/"))
+  (should-not (emacspy--call "test" "syspath" "append" nil '("./tests/") nil))
   (should (py-import "test" "emacspy_test"))
   (should (py-get-object-attr "test" "emacspy_test" "test_obj" "test_obj"))
-  (should (string= "test" (py-call-method "test" "test_obj" "get_string"))) )
+  (should (string= "test" (emacspy--call "test" "test_obj" "get_string" nil nil nil))) )
 
 (ert-deftest ert-test-emacspy-non-existing-interpreter ()
   (should-error (py-run-string "NON_EXISTING" "True")))
@@ -165,7 +174,7 @@
     (puthash 2 nil hash)
     (puthash "list" '(1) hash)
 
-    (puthash "key" 1 nhash)
+    (puthash "key" -1.5 nhash)
     (puthash "hash" nhash hash)
 
     (should (py-set-global "test" empty-hash "test_empty_hash"))
@@ -174,4 +183,12 @@
     (should (py-run-string "test" "test_hash[1]=='test'"))
     (should (py-run-string "test" "test_hash[2]==False"))
     (should (py-run-string "test" "test_hash['list'][0]==1"))
-    (should (py-run-string "test" "test_hash['hash']['key']==1")) ))
+    (should (py-run-string "test" "test_hash['hash']['key']==-1.5"))
+
+    (let ((py-hash (py-get-global-variable "test" "test_hash")))
+      (should (hash-table-p py-hash))
+      (should (string= "test" (gethash 1 py-hash)))
+      (should (eq nil (gethash 2 py-hash)))
+      (should (listp (gethash "list" py-hash)))
+      (should (eq 1 (nth 0 (gethash "list" py-hash)) ))
+      (should (= -1.5 (gethash "key" (gethash "hash" py-hash)))) ) ))