From 46a0ed02409b5607724a5df44c6e83723a0b5d87 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Wed, 6 Aug 2014 17:55:09 -0400 Subject: [PATCH 01/14] adding exec. adding better exception handling and parse exception detection --- .gitignore | 1 + Makefile | 10 ++++++ binding.gyp | 7 ++-- index.js | 44 ++++++++++++++++++++++++- package.json | 10 ++++-- src/binding.cc | 71 ++++++++++++++++++++++++++++++++++++---- test/index.js | 33 +++++++++++++++++++ test/support/__init__.py | 1 + test/support/test.py | 7 ++++ 9 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 Makefile create mode 100644 test/index.js create mode 100644 test/support/__init__.py create mode 100644 test/support/test.py diff --git a/.gitignore b/.gitignore index 378eac2..e3fbd98 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +node_modules diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fe41d5b --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +DEBUG=node-python* +PYTHONPATH=./test/support + +test: + $(MAKE) DEBUG= test-debug + +test-debug: + DEBUG=$(DEBUG) PYTHONPATH=$(PYTHONPATH) ./node_modules/.bin/mocha -R spec + +.PHONY: test test-debug diff --git a/binding.gyp b/binding.gyp index ebeaa63..b8a7cf0 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,14 +7,17 @@ "src/utils.cc", "src/py_object_wrapper.cc" ], + "include_dir": [ + "/Users/matt/development/electronifie/scratch/bondmath/.node-virtualenv/include/python2.7" + ], "conditions": [ ['OS=="mac"', { "xcode_settings": { "OTHER_CFLAGS": [ - " #include @@ -9,16 +8,67 @@ using namespace v8; using namespace node; using std::string; +Handle eval(const Arguments& args) { + HandleScope scope; + if (args.Length() < 1 || !args[0]->IsString()) { + return ThrowException( + Exception::Error(String::New("A string expression must be provided.")) + ); + } + + PyCodeObject* code = (PyCodeObject*) Py_CompileString(*String::Utf8Value(args[0]->ToString()), "test", Py_eval_input); + PyObject* main_module = PyImport_AddModule("__main__"); + PyObject* global_dict = PyModule_GetDict(main_module); + PyObject* local_dict = PyDict_New(); + PyObject* obj = PyEval_EvalCode(code, global_dict, local_dict); + PyObject* result = PyObject_Str(obj); + + return scope.Close(PyObjectWrapper::New(result)); +} + Handle import(const Arguments& args) { HandleScope scope; - if(args.Length() < 1 || !args[0]->IsString()) { + if (args.Length() < 1 || !args[0]->IsString()) { return ThrowException( Exception::Error(String::New("I don't know how to import that.")) ); } - PyObject* module_name = PyString_FromString(*String::Utf8Value(args[0]->ToString())); - PyObject* module = PyImport_Import(module_name); - if(!module) { + + PyObject* module_name; + PyObject* module; + + module_name = PyUnicode_FromString(*String::Utf8Value(args[0]->ToString())); + module = PyImport_Import(module_name); + + if (PyErr_Occurred()) { + + Local errMsg = v8::String::Concat(v8::String::New("Could not import "), args[0]->ToString()); + + PyObject *ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); + char *pStrErrorMessage = PyString_AsString(PyObject_Str(pvalue));; + + if (ptype != NULL) { + errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(ptype)))); + } + + if (pvalue != NULL) { + errMsg = v8::String::Concat(errMsg, v8::String::New("\nPython Error: ")); + errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(pvalue)))); + } + + if (ptraceback != NULL) { + errMsg = v8::String::Concat(errMsg, v8::String::New("\n")); + errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(ptraceback)))); + } + + return ThrowException( + Exception::Error(String::New(*String::Utf8Value(errMsg))) + ); + } + + if (!module) { return ThrowPythonException(); } Py_XDECREF(module_name); @@ -26,13 +76,20 @@ Handle import(const Arguments& args) { return scope.Close(PyObjectWrapper::New(module)); } - void init (Handle exports) { HandleScope scope; Py_Initialize(); PyObjectWrapper::Initialize(); + // how to schedule Py_Finalize(); to be called when process exits? + + // module.exports.import + exports->Set( + String::NewSymbol("eval"), + FunctionTemplate::New(eval)->GetFunction() + ); + // module.exports.import exports->Set( String::NewSymbol("import"), @@ -47,4 +104,4 @@ void init (Handle exports) { } -NODE_MODULE(binding, init) +NODE_MODULE(binding, init) \ No newline at end of file diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..400b2c7 --- /dev/null +++ b/test/index.js @@ -0,0 +1,33 @@ +var python = require('../'); +var PythonError = python.PythonError; +var should = require('should'); + +describe('node-python', function () { + describe('eval', function () { + it('should return resulting value from python statement executed', function () { + var value = python.eval('"1"'); + value.should.equal("1"); + }); + it('should return resulting value from python statement executed, converting to string with complex types', function () { + var decimal = python.import('decimal'); + var smallNum = decimal.Decimal('0.0000000001'); + smallNum.toString().should.equal('1E-10'); + }); + }); + describe('import', function () { + it('should return object representing module imported, containing functions from imported module', function () { + var value = python.import('decimal'); + value.should.have.property('valueOf'); + }); + it('should throw a PythonError when importing a module that does not exist', function () { + should(function () { + python.import('jibberish'); + }).throw(/No module named jibberish/); + }); + it('should throw an Error when importing a module that includes bad syntax', function () { + should(function () { + python.import('test'); + }).throw(/exceptions.SyntaxError/) + }); + }); +}); diff --git a/test/support/__init__.py b/test/support/__init__.py new file mode 100644 index 0000000..ec7dc75 --- /dev/null +++ b/test/support/__init__.py @@ -0,0 +1 @@ +__author__ = 'matt walters' diff --git a/test/support/test.py b/test/support/test.py new file mode 100644 index 0000000..3ea5477 --- /dev/null +++ b/test/support/test.py @@ -0,0 +1,7 @@ +class Good(): + def good(self): + return 0; + +class Bad(): + def bad(self): + should cause parse error From 96c578c30da84454ea30131d62499fe3b2e774e8 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Thu, 7 Aug 2014 13:29:14 -0400 Subject: [PATCH 02/14] improving error handling. adding python stacktrace --- src/py_object_wrapper.cc | 12 ++++++-- src/utils.cc | 61 +++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 370b391..74a88d8 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -1,4 +1,3 @@ - #include "py_object_wrapper.h" #include "utils.h" @@ -74,7 +73,6 @@ Handle PyObjectWrapper::New(PyObject* obj) { else { Py_XDECREF(obj); } - return scope.Close(jsVal); } @@ -221,9 +219,17 @@ Handle PyObjectWrapper::InstanceCall(const Arguments& args) { PyObject* args_tuple = PyTuple_New(len); for(int i = 0; i < len; ++i) { PyObject* py_arg = ConvertToPython(args[i]); + if (PyErr_Occurred()) { + return ThrowPythonException(); + } PyTuple_SET_ITEM(args_tuple, i, py_arg); } PyObject* result = PyObject_CallObject(mPyObject, args_tuple); + + if (PyErr_Occurred()) { + return ThrowPythonException(); + } + Py_XDECREF(args_tuple); if(result) { return scope.Close(PyObjectWrapper::New(result)); @@ -243,6 +249,6 @@ PyObject* PyObjectWrapper::InstanceGet(const string& key) { if(PyObject_HasAttrString(mPyObject, key.c_str())) { PyObject* attribute = PyObject_GetAttrString(mPyObject, key.c_str()); return attribute; - } + } return (PyObject*)NULL; } diff --git a/src/utils.cc b/src/utils.cc index f4a8274..c5afbcb 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -7,26 +7,73 @@ Handle ThrowPythonException() { PyObject *ptype, *pvalue, *ptraceback; PyErr_Fetch(&ptype, &pvalue, &ptraceback); + PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); // maybe useless to protect against bad use of ThrowPythonException ? - if(!ptype) { + if(pvalue == NULL) { return ThrowException( Exception::Error(String::New("No exception found")) ); } // handle exception message - Local msg; - if(pvalue && PyObject_TypeCheck(pvalue, &PyString_Type)) { - msg = String::New(PyString_AsString(pvalue)); + Local msg = String::New("Python Error: "); + + if (ptype != NULL) { + msg = v8::String::Concat(msg, v8::String::New(PyString_AsString(PyObject_Str(PyObject_GetAttrString(ptype, "__name__"))))); + msg = v8::String::Concat(msg, v8::String::New(": ")); + + if (pvalue != NULL) { + msg = v8::String::Concat(msg, v8::String::New(PyString_AsString(PyObject_Str(pvalue)))); + } + + msg = v8::String::Concat(msg, v8::String::New("\n")); + } + + if (ptraceback != NULL) { + + PyObject *module_name, *pyth_module, *pyth_func; + module_name = PyString_FromString("traceback"); + pyth_module = PyImport_Import(module_name); + + Py_DECREF(module_name); + + pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); + + Py_DECREF(pyth_module); + + if (pyth_func) { + PyObject *pyth_val, *pystr, *ret; + char *str; + + char *full_backtrace; + + pyth_val = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL); + ret = PyUnicode_Join(PyUnicode_FromString(""), pyth_val); + pystr = PyObject_Str(ret); + str = PyString_AsString(pystr); + full_backtrace = strdup(str); + + Py_DECREF(pyth_func); + Py_DECREF(pyth_val); + Py_DECREF(pystr); + Py_DECREF(str); + + msg = v8::String::Concat(msg, v8::String::New("\n")); + msg = v8::String::Concat(msg, v8::String::New(full_backtrace)); + } else { + msg = v8::String::Concat(msg, v8::String::New("\n")); + msg = v8::String::Concat(msg, v8::String::New(PyString_AsString(PyObject_Str(ptraceback)))); + } + } Local err; - if(PyErr_GivenExceptionMatches(ptype, PyExc_ReferenceError)) { + if (PyErr_GivenExceptionMatches(ptype, PyExc_ReferenceError)) { err = Exception::ReferenceError(msg); } - else if(PyErr_GivenExceptionMatches(ptype, PyExc_SyntaxError)) { + else if (PyErr_GivenExceptionMatches(ptype, PyExc_SyntaxError)) { err = Exception::SyntaxError(msg); } - else if(PyErr_GivenExceptionMatches(ptype, PyExc_TypeError)) { + else if (PyErr_GivenExceptionMatches(ptype, PyExc_TypeError)) { err = Exception::TypeError(msg); } else { From 11cbdc2b88f361cc27ef1d3809a79451d3e599c0 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Thu, 7 Aug 2014 13:31:08 -0400 Subject: [PATCH 03/14] updating tests --- src/binding.cc | 25 +------------------------ test/index.js | 2 +- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/binding.cc b/src/binding.cc index 2bba4c4..c057ef6 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -42,30 +42,7 @@ Handle import(const Arguments& args) { if (PyErr_Occurred()) { - Local errMsg = v8::String::Concat(v8::String::New("Could not import "), args[0]->ToString()); - - PyObject *ptype, *pvalue, *ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); - char *pStrErrorMessage = PyString_AsString(PyObject_Str(pvalue));; - - if (ptype != NULL) { - errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(ptype)))); - } - - if (pvalue != NULL) { - errMsg = v8::String::Concat(errMsg, v8::String::New("\nPython Error: ")); - errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(pvalue)))); - } - - if (ptraceback != NULL) { - errMsg = v8::String::Concat(errMsg, v8::String::New("\n")); - errMsg = v8::String::Concat(errMsg, v8::String::New(PyString_AsString(PyObject_Str(ptraceback)))); - } - - return ThrowException( - Exception::Error(String::New(*String::Utf8Value(errMsg))) - ); + return ThrowPythonException(); } if (!module) { diff --git a/test/index.js b/test/index.js index 400b2c7..17cbecc 100644 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ describe('node-python', function () { it('should throw an Error when importing a module that includes bad syntax', function () { should(function () { python.import('test'); - }).throw(/exceptions.SyntaxError/) + }).throw(/Python Error: SyntaxError/) }); }); }); From 0b2d55343826c70b3f06e739adb7ccaa2e197734 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Mon, 11 Aug 2014 12:20:58 -0400 Subject: [PATCH 04/14] fixing translation from js array to python list. --- .gitignore | 4 +++ binding.gyp | 6 ++-- src/binding.cc | 4 +-- src/py_object_wrapper.cc | 71 ++++++++++++++++++++++++++-------------- src/utils.cc | 2 -- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index e3fbd98..5990836 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ build node_modules +.DS_Store +.cproject +.project +.settings diff --git a/binding.gyp b/binding.gyp index b8a7cf0..5ee5f59 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,16 +8,16 @@ "src/py_object_wrapper.cc" ], "include_dir": [ - "/Users/matt/development/electronifie/scratch/bondmath/.node-virtualenv/include/python2.7" + "../bondmath/.node-virtualenv/include/python2.7" ], "conditions": [ ['OS=="mac"', { "xcode_settings": { "OTHER_CFLAGS": [ - "-I/Users/matt/development/electronifie/scratch/bondmath/.node-virtualenv/include/python2.7 -I/Users/matt/development/electronifie/scratch/bondmath/.node-virtualenv/include/python2.7 -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -arch i386 -g -Os -pipe -fno-common -fno-strict-aliasing -fwrapv -DENABLE_DTRACE -DMACOSX -DNDEBUG -Wall -Wstrict-prototypes -Wshorten-64-to-32 -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE" + "-I../../scratch/bondmath/.node-virtualenv/include/python2.7 -I../../scratch/bondmath/.node-virtualenv/include/python2.7 -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -arch i386 -g -Os -pipe -fno-common -fno-strict-aliasing -fwrapv -DENABLE_DTRACE -DMACOSX -DNDEBUG -Wall -Wstrict-prototypes -Wshorten-64-to-32 -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE" ], "OTHER_LDFLAGS": [ - "-L/Users/matt/development/electronifie/scratch/bondmath/.node-virtualenv/lib/python2.7/config/ -ldl -framework CoreFoundation -lpython2.7" + "-L../../scratch/bondmath/.node-virtualenv/lib/python2.7/config/ -ldl -framework CoreFoundation -lpython2.7" ] } }, { # not OSX diff --git a/src/binding.cc b/src/binding.cc index c057ef6..58e3f1d 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -16,7 +16,7 @@ Handle eval(const Arguments& args) { ); } - PyCodeObject* code = (PyCodeObject*) Py_CompileString(*String::Utf8Value(args[0]->ToString()), "test", Py_eval_input); + PyCodeObject* code = (PyCodeObject*) Py_CompileString(*String::Utf8Value(args[0]->ToString()), "eval", Py_eval_input); PyObject* main_module = PyImport_AddModule("__main__"); PyObject* global_dict = PyModule_GetDict(main_module); PyObject* local_dict = PyDict_New(); @@ -81,4 +81,4 @@ void init (Handle exports) { } -NODE_MODULE(binding, init) \ No newline at end of file +NODE_MODULE(binding, init) diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 74a88d8..1d2b2a5 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -1,3 +1,4 @@ +#include #include "py_object_wrapper.h" #include "utils.h" @@ -180,32 +181,51 @@ PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { } else if(value->IsNumber()) { return PyFloat_FromDouble(value->NumberValue()); } else if(value->IsObject()) { - Local obj = value->ToObject(); - if(!obj->FindInstanceInPrototypeChain(PyObjectWrapper::py_function_template).IsEmpty()) { - PyObjectWrapper* python_object = ObjectWrap::Unwrap(value->ToObject()); - PyObject* pyobj = python_object->InstanceGetPyObject(); - return pyobj; - } else { - Local property_names = obj->GetPropertyNames(); - len = property_names->Length(); - PyObject* py_dict = PyDict_New(); - for(int i = 0; i < len; ++i) { - Local str = property_names->Get(i)->ToString(); - Local js_val = obj->Get(str); - PyDict_SetItemString(py_dict, *String::Utf8Value(str), ConvertToPython(js_val)); - } - return py_dict; - } + if(value->IsArray()) { + Local array = Array::Cast(*value); + len = array->Length(); + PyObject* py_list = PyList_New(len); + for(int i = 0; i < len; ++i) { + Local obj = array->Get(i)->ToObject(); + if (!obj->FindInstanceInPrototypeChain(PyObjectWrapper::py_function_template).IsEmpty()) { + PyObjectWrapper* python_object = ObjectWrap::Unwrap(obj); + PyObject* pyobj = python_object->InstanceGetPyObject(); + PyList_SET_ITEM(py_list, i, pyobj); + } else { + Local js_val = array->Get(i); + PyList_SET_ITEM(py_list, i, ConvertToPython(js_val)); + } + } + return py_list; + } else { + Local obj = value->ToObject(); + if(!obj->FindInstanceInPrototypeChain(PyObjectWrapper::py_function_template).IsEmpty()) { + PyObjectWrapper* python_object = ObjectWrap::Unwrap(value->ToObject()); + PyObject* pyobj = python_object->InstanceGetPyObject(); + return pyobj; + } else { + Local property_names = obj->GetPropertyNames(); + len = property_names->Length(); + PyObject* py_dict = PyDict_New(); + for(int i = 0; i < len; ++i) { + Local str = property_names->Get(i)->ToString(); + Local js_val = obj->Get(str); + PyDict_SetItemString(py_dict, *String::Utf8Value(str), ConvertToPython(js_val)); + } + return py_dict; + } + } + return NULL; } else if(value->IsArray()) { - Local array = Array::Cast(*value); - len = array->Length(); - PyObject* py_list = PyList_New(len); - for(int i = 0; i < len; ++i) { - Local js_val = array->Get(i); - PyList_SET_ITEM(py_list, i, ConvertToPython(js_val)); - } - return py_list; + Local array = Array::Cast(*value); + len = array->Length(); + PyObject* py_list = PyList_New(len); + for(int i = 0; i < len; ++i) { + Local js_val = array->Get(i); + PyList_SET_ITEM(py_list, i, ConvertToPython(js_val)); + } + return py_list; } else if(value->IsUndefined()) { Py_RETURN_NONE; } @@ -217,6 +237,7 @@ Handle PyObjectWrapper::InstanceCall(const Arguments& args) { HandleScope scope; int len = args.Length(); PyObject* args_tuple = PyTuple_New(len); + for(int i = 0; i < len; ++i) { PyObject* py_arg = ConvertToPython(args[i]); if (PyErr_Occurred()) { @@ -225,12 +246,12 @@ Handle PyObjectWrapper::InstanceCall(const Arguments& args) { PyTuple_SET_ITEM(args_tuple, i, py_arg); } PyObject* result = PyObject_CallObject(mPyObject, args_tuple); - if (PyErr_Occurred()) { return ThrowPythonException(); } Py_XDECREF(args_tuple); + if(result) { return scope.Close(PyObjectWrapper::New(result)); } else { diff --git a/src/utils.cc b/src/utils.cc index c5afbcb..dd395e1 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -35,9 +35,7 @@ Handle ThrowPythonException() { pyth_module = PyImport_Import(module_name); Py_DECREF(module_name); - pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); - Py_DECREF(pyth_module); if (pyth_func) { From 17afa4150b661459b582b17059b47a425dc52ab5 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Tue, 12 Aug 2014 14:37:55 -0400 Subject: [PATCH 05/14] adding finalize and guard clauses against running python code after interpreter is finalized --- index.js | 41 +++++++++++++++++++++++++++++++---------- src/binding.cc | 19 ++++++++++++++++++- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index d1562e7..2c6d5fc 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ var binding = require('bindings')('binding.node'); var debug = require('debug')('node-python'); +var warn = require('debug')('node-python:warn'); var util = require('util'); function PythonError (message, value) { @@ -18,22 +19,42 @@ util.inherits(PythonError, Error); module.exports.PythonError = PythonError; +var finalized = false; +function pythonFinalized () { + if (finalized) { + warn('node-python\'s python interpreter has already been finalized. python code cannot be executed.'); + } + return finalized; +} + module.exports.eval = function (string) { + if (pythonFinalized()) throw new PythonError('node-python\'s python interpreter has already been finalized. python code cannot be executed.'); return binding.eval(string); } -var _import = module.exports.import = function (string) { -var result = null; - -try { - result = binding.import(string); -} catch (e) { - e = new PythonError(e); - debug(e); - throw e; +module.exports.finalize = function () { + if ( ! pythonFinalized()) { + binding.finalize(); + finalized = true; + return finalized; + } + return false; } -return result; +var _import = module.exports.import = function (string) { + if (pythonFinalized()) throw new PythonError('node-python\'s python interpreter has already been finalized. python code cannot be executed.'); + + var result = null; + + try { + result = binding.import(string); + } catch (e) { + e = new PythonError(e); + debug(e); + throw e; + } + + return result; } var os = _import('os'); diff --git a/src/binding.cc b/src/binding.cc index 58e3f1d..3c1990a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -23,9 +23,20 @@ Handle eval(const Arguments& args) { PyObject* obj = PyEval_EvalCode(code, global_dict, local_dict); PyObject* result = PyObject_Str(obj); + Py_XDECREF(code); + Py_XDECREF(global_dict); + Py_XDECREF(local_dict); + Py_XDECREF(obj); + return scope.Close(PyObjectWrapper::New(result)); } +Handle finalize(const Arguments& args) { + HandleScope scope; + Py_Finalize(); + return scope.Close(Undefined()); +} + Handle import(const Arguments& args) { HandleScope scope; if (args.Length() < 1 || !args[0]->IsString()) { @@ -61,12 +72,18 @@ void init (Handle exports) { // how to schedule Py_Finalize(); to be called when process exits? - // module.exports.import + // module.exports.eval exports->Set( String::NewSymbol("eval"), FunctionTemplate::New(eval)->GetFunction() ); + // module.exports.finalize + exports->Set( + String::NewSymbol("finalize"), + FunctionTemplate::New(finalize)->GetFunction() + ); + // module.exports.import exports->Set( String::NewSymbol("import"), From dcab20461fbe1d6d82eb5533387d7a48fb5d3057 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Tue, 12 Aug 2014 15:27:48 -0400 Subject: [PATCH 06/14] fixing translation of booleans --- src/py_object_wrapper.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 1d2b2a5..42b7b68 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -178,7 +178,13 @@ PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { HandleScope scope; if(value->IsString()) { return PyString_FromString(*String::Utf8Value(value->ToString())); - } else if(value->IsNumber()) { + } else if (value->IsBoolean()) { + if (value->ToBoolean()->IsTrue()) { + return Py_True; + } else { + return Py_False; + } + } else if(value->IsNumber()) { return PyFloat_FromDouble(value->NumberValue()); } else if(value->IsObject()) { if(value->IsArray()) { From c08c54a446175b8a0d384c9c1c676c3fa8a7613e Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Fri, 15 Aug 2014 10:41:00 -0400 Subject: [PATCH 07/14] resetting flags commands to default --- binding.gyp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/binding.gyp b/binding.gyp index 5ee5f59..ebeaa63 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,17 +7,14 @@ "src/utils.cc", "src/py_object_wrapper.cc" ], - "include_dir": [ - "../bondmath/.node-virtualenv/include/python2.7" - ], "conditions": [ ['OS=="mac"', { "xcode_settings": { "OTHER_CFLAGS": [ - "-I../../scratch/bondmath/.node-virtualenv/include/python2.7 -I../../scratch/bondmath/.node-virtualenv/include/python2.7 -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -arch i386 -g -Os -pipe -fno-common -fno-strict-aliasing -fwrapv -DENABLE_DTRACE -DMACOSX -DNDEBUG -Wall -Wstrict-prototypes -Wshorten-64-to-32 -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE" + " Date: Mon, 8 Sep 2014 14:13:19 -0400 Subject: [PATCH 08/14] fixed conversion of js dates to python datetimes --- src/binding.cc | 7 +++-- src/py_object_wrapper.cc | 14 +++++++++- test/index.js | 57 ++++++++++++++++++++++++++++++++++++++- test/support/test.py | 2 +- test/support/test2.py | 7 +++++ test/support/test2.pyc | Bin 0 -> 782 bytes 6 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 test/support/test2.py create mode 100644 test/support/test2.pyc diff --git a/src/binding.cc b/src/binding.cc index 3c1990a..0ee4e2b 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,6 +1,6 @@ #include #include - +#include #include "py_object_wrapper.h" #include "utils.h" @@ -52,7 +52,6 @@ Handle import(const Arguments& args) { module = PyImport_Import(module_name); if (PyErr_Occurred()) { - return ThrowPythonException(); } @@ -66,8 +65,8 @@ Handle import(const Arguments& args) { void init (Handle exports) { HandleScope scope; - Py_Initialize(); + Py_Initialize(); PyObjectWrapper::Initialize(); // how to schedule Py_Finalize(); to be called when process exits? @@ -98,4 +97,4 @@ void init (Handle exports) { } -NODE_MODULE(binding, init) +NODE_MODULE(binding, init) \ No newline at end of file diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 42b7b68..885cfeb 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -1,11 +1,15 @@ #include #include "py_object_wrapper.h" #include "utils.h" +#include "datetime.h" Persistent PyObjectWrapper::py_function_template; void PyObjectWrapper::Initialize() { HandleScope scope; + + PyDateTime_IMPORT; + Local fn_tpl = FunctionTemplate::New(); Local proto = fn_tpl->PrototypeTemplate(); Local obj_tpl = fn_tpl->InstanceTemplate(); @@ -176,6 +180,7 @@ Handle PyObjectWrapper::ValueOf(const Arguments& args) { PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { int len; HandleScope scope; + if(value->IsString()) { return PyString_FromString(*String::Utf8Value(value->ToString())); } else if (value->IsBoolean()) { @@ -186,6 +191,14 @@ PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { } } else if(value->IsNumber()) { return PyFloat_FromDouble(value->NumberValue()); + } else if(value->IsDate()) { + Handle date = Handle::Cast(value); + PyObject* floatObj = PyFloat_FromDouble(date->NumberValue() / 1000.0 ); // javascript returns milliseconds since epoch. python wants seconds since epoch + PyObject* timeTuple = Py_BuildValue("(O)", floatObj); + Py_DECREF(floatObj); + PyObject* dateTime = PyDateTime_FromTimestamp(timeTuple); + Py_DECREF(timeTuple); + return dateTime; } else if(value->IsObject()) { if(value->IsArray()) { Local array = Array::Cast(*value); @@ -221,7 +234,6 @@ PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { return py_dict; } } - return NULL; } else if(value->IsArray()) { Local array = Array::Cast(*value); diff --git a/test/index.js b/test/index.js index 17cbecc..2555222 100644 --- a/test/index.js +++ b/test/index.js @@ -30,4 +30,59 @@ describe('node-python', function () { }).throw(/Python Error: SyntaxError/) }); }); -}); + it('should convert javascript booleans to python booleans', function () { + test = python.import('test2'); + var type = test.getPythonTypeName(true); + type.should.equal('bool'); + }); + it('should convert javascript date to python date', function () { + test = python.import('test2'); + var type = test.getPythonTypeName(new Date()); + type.should.equal('datetime'); + }); + it('should convert javascript numbers to python floats', function () { + test = python.import('test2'); + var type = test.getPythonTypeName(1); + type.should.equal('float'); + }); + it('should convert javascript arrays to python list', function () { + test = python.import('test2'); + var type = test.getPythonTypeName([]); + type.should.equal('list'); + }); + it('should convert javascript objects to python dictionaries', function () { + test = python.import('test2'); + var type = test.getPythonTypeName({}); + type.should.equal('dict'); + }); + it('should convert javascript nested objects correctly', function () { + test = python.import('test2'); + var type = test.getPythonTypeName2({ + value: 1 + }, 'value'); + type.should.equal('float'); + var type = test.getPythonTypeName2({ + value: true + }, 'value'); + type.should.equal('bool'); + var type = test.getPythonTypeName2({ + value: new Date() + }, 'value'); + type.should.equal('datetime'); + var type = test.getPythonTypeName2({ + value: {} + }, 'value'); + type.should.equal('dict'); + var type = test.getPythonTypeName2({ + value: ['one', 'two', 'three'] + }, 'value'); + type.should.equal('list'); + var i = 0, arr = []; + while (i < 10000) { + arr.push(Math.random().toString()) + i++; + } + var type = test.getPythonTypeName(arr); + type.should.equal('list'); + }); +}); \ No newline at end of file diff --git a/test/support/test.py b/test/support/test.py index 3ea5477..5fbea7f 100644 --- a/test/support/test.py +++ b/test/support/test.py @@ -4,4 +4,4 @@ def good(self): class Bad(): def bad(self): - should cause parse error + should cause parse error \ No newline at end of file diff --git a/test/support/test2.py b/test/support/test2.py new file mode 100644 index 0000000..7d8fe71 --- /dev/null +++ b/test/support/test2.py @@ -0,0 +1,7 @@ +def getPythonTypeName(value): + return type(value).__name__ +def getPythonTypeName2(value, index): + item = value[index] + return type(item).__name__ +def getPythonValue(value): + return value \ No newline at end of file diff --git a/test/support/test2.pyc b/test/support/test2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c6520c21e380b2efa86935d112a9d0950067bc6 GIT binary patch literal 782 zcmcIiO-lnY5S{G$(H1J`MLc=kiw1fY5d@D#*6P8_QZ}Qm>=(_nSg0rcLH-+mh=0I0 zu@*s(HZYS(<|TP=ChqFV$e1cqD`f{0v9SIGu2xt>sq zVCOL3hZCX&(Tkx+1DvLZi2_8A$h>9gAjqcLm$JaeUOy<)Nvquovs6ZQD#FEC$EO?Bc3>mdk({fx-}P zD>JXe$mBZ3u$7Q~ETCdx=-@dDlA=E(r9e@t7bSW+4Kh_rQ9FJLf(zklJ)-vT&M#2| z<>9r`WO`=RAf*@@q#5Y??u+6=2Lko=@Z7;0E1oyn WPw3$0ld5skl25}2(1LWMZu|*|WU*ub literal 0 HcmV?d00001 From 35ea668db3c2733fd43bc4cce33531679ca02b71 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Tue, 9 Sep 2014 09:43:20 -0400 Subject: [PATCH 09/14] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0cd62e1..4deb69e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-python", - "version": "0.0.4", + "version": "v0.0.5-rc2", "description": "Call python stuff from nodejs", "main": "index.js", "repository": { From 88ac44ae27cbe59b9fe9a46063073625d2f9e239 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Tue, 9 Sep 2014 14:40:23 -0400 Subject: [PATCH 10/14] adding conversion from js null and undefined to python NoneType --- src/py_object_wrapper.cc | 2 ++ test/index.js | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 885cfeb..84bdc16 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -189,6 +189,8 @@ PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { } else { return Py_False; } + } else if (value->IsNull() || value->IsUndefined()) { + return Py_None; } else if(value->IsNumber()) { return PyFloat_FromDouble(value->NumberValue()); } else if(value->IsDate()) { diff --git a/test/index.js b/test/index.js index 2555222..7749595 100644 --- a/test/index.js +++ b/test/index.js @@ -30,6 +30,16 @@ describe('node-python', function () { }).throw(/Python Error: SyntaxError/) }); }); + it('should convert javascript null to python NoneType', function () { + test = python.import('test2'); + var type = test.getPythonTypeName(null); + type.should.equal('NoneType'); + }); + it('should convert javascript undefined to python NoneType', function () { + test = python.import('test2'); + var type = test.getPythonTypeName(undefined); + type.should.equal('NoneType'); + }); it('should convert javascript booleans to python booleans', function () { test = python.import('test2'); var type = test.getPythonTypeName(true); From ba2bafd13a93713a3d5717596e390af674edabdb Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Tue, 9 Sep 2014 14:40:48 -0400 Subject: [PATCH 11/14] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4deb69e..c01c48c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-python", - "version": "v0.0.5-rc2", + "version": "v0.0.5-rc3", "description": "Call python stuff from nodejs", "main": "index.js", "repository": { From 8a9342a0d9054bb37992ec8481c50984c2014f22 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Wed, 29 Oct 2014 14:17:29 -0400 Subject: [PATCH 12/14] converting python lists to js arrays and python dicts to js objects --- npm-debug.log | 42 ++++++++++++++++++++++++++++++++++++++++ src/py_object_wrapper.cc | 22 +++++++++++++++++++++ test/index.js | 14 ++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 npm-debug.log diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..7cd3f50 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,42 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'install' ] +2 info using npm@1.4.28 +3 info using node@v0.10.32 +4 verbose readDependencies using package.json deps +5 verbose install where, deps [ '/Users/matt/development/electronifie/node-python', +5 verbose install [ 'bindings', 'debug', 'mocha', 'should', 'sinon' ] ] +6 info preinstall node-python@0.0.5-rc3 +7 verbose readDependencies using package.json deps +8 verbose already installed skipping bindings@~1.1.1 /Users/matt/development/electronifie/node-python +9 verbose already installed skipping debug@^1.0.4 /Users/matt/development/electronifie/node-python +10 verbose already installed skipping mocha@^1.21.3 /Users/matt/development/electronifie/node-python +11 verbose already installed skipping should@^4.0.4 /Users/matt/development/electronifie/node-python +12 verbose already installed skipping sinon@^1.10.3 /Users/matt/development/electronifie/node-python +13 silly resolved [] +14 info build /Users/matt/development/electronifie/node-python +15 verbose linkStuff [ false, false, false, '/Users/matt/development/electronifie' ] +16 info linkStuff node-python@0.0.5-rc3 +17 verbose linkBins node-python@0.0.5-rc3 +18 verbose linkMans node-python@0.0.5-rc3 +19 verbose rebuildBundles node-python@0.0.5-rc3 +20 verbose rebuildBundles [ '.bin', 'bindings', 'debug', 'mocha', 'should', 'sinon' ] +21 info install node-python@0.0.5-rc3 +22 verbose unsafe-perm in lifecycle true +23 info node-python@0.0.5-rc3 Failed to exec install script +24 error node-python@0.0.5-rc3 install: `node-gyp rebuild` +24 error Exit status 1 +25 error Failed at the node-python@0.0.5-rc3 install script. +25 error This is most likely a problem with the node-python package, +25 error not with npm itself. +25 error Tell the author that this fails on your system: +25 error node-gyp rebuild +25 error You can get their info via: +25 error npm owner ls node-python +25 error There is likely additional logging output above. +26 error System Darwin 13.3.0 +27 error command "/usr/local/bin/node" "/usr/local/bin/npm" "install" +28 error cwd /Users/matt/development/electronifie/node-python +29 error node -v v0.10.32 +30 error npm -v 1.4.28 +31 error code ELIFECYCLE +32 verbose exit [ 1, true ] diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 84bdc16..a8f472f 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -39,6 +39,28 @@ Handle PyObjectWrapper::New(PyObject* obj) { if(obj == Py_None) { jsVal = Local::New(Undefined()); } + else if(PyDict_Check(obj)) { + Local dict = v8::Object::New(); + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next(obj, &pos, &key, &value)) { + Handle jsKey = PyObjectWrapper::New(key); + Handle jsValue = PyObjectWrapper::New(value); + dict->Set(jsKey, jsValue); + } + jsVal = dict; + } + else if(PyList_CheckExact(obj)) { + int size = PyList_Size(obj); + Local array = v8::Array::New(size); + PyObject* value; + for(int i = 0; i < size; i++ ){ + value = PyList_GetItem(obj, i); + Handle jsValue = PyObjectWrapper::New(value); + array->Set(i, jsValue); + } + jsVal = array; + } // double else if(PyFloat_CheckExact(obj)) { double d = PyFloat_AsDouble(obj); diff --git a/test/index.js b/test/index.js index 7749595..5d878d8 100644 --- a/test/index.js +++ b/test/index.js @@ -95,4 +95,18 @@ describe('node-python', function () { var type = test.getPythonTypeName(arr); type.should.equal('list'); }); + it('should convert python dicts to javascript objects', function () { + test = python.import('test2'); + var value = test.getPythonValue({ + value: 1 + }); + value.should.have.property('value', 1); + }); + it('should convert python lists to javascript arrays', function () { + test = python.import('test2'); + var value = test.getPythonValue([ 1, 2, 3]); + value.should.containEql(1); + value.should.containEql(2); + value.should.containEql(3); + }); }); \ No newline at end of file From f8b67417166743e46763659ca8dc186a4579144e Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Wed, 29 Oct 2014 14:18:50 -0400 Subject: [PATCH 13/14] removing log file --- npm-debug.log | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 npm-debug.log diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index 7cd3f50..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,42 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'install' ] -2 info using npm@1.4.28 -3 info using node@v0.10.32 -4 verbose readDependencies using package.json deps -5 verbose install where, deps [ '/Users/matt/development/electronifie/node-python', -5 verbose install [ 'bindings', 'debug', 'mocha', 'should', 'sinon' ] ] -6 info preinstall node-python@0.0.5-rc3 -7 verbose readDependencies using package.json deps -8 verbose already installed skipping bindings@~1.1.1 /Users/matt/development/electronifie/node-python -9 verbose already installed skipping debug@^1.0.4 /Users/matt/development/electronifie/node-python -10 verbose already installed skipping mocha@^1.21.3 /Users/matt/development/electronifie/node-python -11 verbose already installed skipping should@^4.0.4 /Users/matt/development/electronifie/node-python -12 verbose already installed skipping sinon@^1.10.3 /Users/matt/development/electronifie/node-python -13 silly resolved [] -14 info build /Users/matt/development/electronifie/node-python -15 verbose linkStuff [ false, false, false, '/Users/matt/development/electronifie' ] -16 info linkStuff node-python@0.0.5-rc3 -17 verbose linkBins node-python@0.0.5-rc3 -18 verbose linkMans node-python@0.0.5-rc3 -19 verbose rebuildBundles node-python@0.0.5-rc3 -20 verbose rebuildBundles [ '.bin', 'bindings', 'debug', 'mocha', 'should', 'sinon' ] -21 info install node-python@0.0.5-rc3 -22 verbose unsafe-perm in lifecycle true -23 info node-python@0.0.5-rc3 Failed to exec install script -24 error node-python@0.0.5-rc3 install: `node-gyp rebuild` -24 error Exit status 1 -25 error Failed at the node-python@0.0.5-rc3 install script. -25 error This is most likely a problem with the node-python package, -25 error not with npm itself. -25 error Tell the author that this fails on your system: -25 error node-gyp rebuild -25 error You can get their info via: -25 error npm owner ls node-python -25 error There is likely additional logging output above. -26 error System Darwin 13.3.0 -27 error command "/usr/local/bin/node" "/usr/local/bin/npm" "install" -28 error cwd /Users/matt/development/electronifie/node-python -29 error node -v v0.10.32 -30 error npm -v 1.4.28 -31 error code ELIFECYCLE -32 verbose exit [ 1, true ] From b32b9ec0b5babae84b5c5403286f772da4eb0e7e Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Wed, 29 Oct 2014 14:20:23 -0400 Subject: [PATCH 14/14] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c01c48c..46b4829 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-python", - "version": "v0.0.5-rc3", + "version": "v0.0.5-rc4", "description": "Call python stuff from nodejs", "main": "index.js", "repository": {