From 6e16726946f7051c2010c154d98c03d0a8ae6955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Se=CC=81bastien=20Tremblay?= Date: Sat, 22 Dec 2012 01:00:56 -0500 Subject: [PATCH 01/27] update compile with node-gyp + release on npm --- .gitignore | 1 + README.md | 31 ++++++++++++++++--------------- binding.gyp | 13 +++++++++++++ index.js | 3 +++ package.json | 34 ++++++++++++++++++++++++++++++---- wscript | 34 ---------------------------------- 6 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 .gitignore create mode 100644 binding.gyp create mode 100644 index.js delete mode 100644 wscript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/README.md b/README.md index 09bdcc2..719122d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,18 @@ -node-python binding -====================== +# node-python binding -I bumped this up from "playground" to "binding" on account of it starting to feel like the +python bridge for nodejs! + +Forked from [chrisdickinson/node-python](https://github.com/chrisdickinson/node-python) and updated to use node-gyp + +## Installation + +```npm install node-python``` + +Tested on OSX 10.7.4 with node 0.8.15 + +## Usage + +[I](https://github.com/chrisdickinson) bumped this up from "playground" to "binding" on account of it starting to feel like the right thing to do. This is a binding between Node.js and Python; unfortunately as written it actually embeds a @@ -31,19 +42,9 @@ you have to cast them from whatever they are into whatever you want them to be. only provided cast is "toString", but that should change in the near future (hopefully). Passing python objects that you get from calling python functions from javascript can seamlessly -be passed back into python functions (no casting required). Currently there's what I assume to be +be passed back into python functions (no casting required). Currently there's what [I](https://github.com/chrisdickinson) assume to be a passable argument translation implementation for simple Objects (ones that act like dicts), Arrays, Numbers (maybe?), and Strings. You can slap together a tiny WSGI hosting thing on it, as well, which is provided in `wsgi.js`. -It's half implemented, but it's midnight on a Sunday and I should probably sleep. - -Installation ------------- - -I've only tested this out on my computer so no huge promises can be made about other platforms. -Running OSX 10.6.3: - - node-waf configure build - -In theory this is all you need in the entire world. +It's half implemented, but it's midnight on a Sunday and [I](https://github.com/chrisdickinson) should probably sleep. diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..20c6278 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,13 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ], + "link_settings": { + "libraries": [ + "$(SDKROOT)/System/Library/Frameworks/Python.framework" + ] + } + } + ] +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..a88cdf1 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ + +module.exports = require('./build/Release/binding.node'); + diff --git a/package.json b/package.json index b6837ae..911c65f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,31 @@ -{ "name" : "node_test" -, "version" : "0.0.1" -, "description" : "playing around with node" -, "author": "Chris Dickinson" +{ + "name": "node-python", + "version": "0.0.2", + "description": "Call python stuff from nodejs", + "main": "index.js", + "scripts": { + "install": "node-gyp rebuild" + }, + "repository": { + "type": "git", + "url": "git://github.com/JeanSebTr/node-python.git" + }, + "keywords": [ + "python", + "bridge" + ], + "author": { + "name": "Jean-Sébastien Tremblay", + "email": "jeansebtr@xpensia.com", + "url": "http://blog.jeansebtr.com" + }, + "contributors": [ + { + "name": "Chris Dickinson", + "email": "chris@neversaw.us" + } + ], + "license": "BSD", + "gypfile": true, + "readmeFilename": "README.md" } diff --git a/wscript b/wscript deleted file mode 100644 index 1b1f060..0000000 --- a/wscript +++ /dev/null @@ -1,34 +0,0 @@ -import Options -from os import unlink, symlink, popen -from os.path import exists - -srcdir = '.' -blddir = 'build' -VERSION = '0.0.1' - -def set_options(opt): - opt.tool_options('compiler_cxx') - opt.tool_options('python') - -def configure(conf): - conf.check_tool('compiler_cxx') - conf.check_tool('node_addon') - conf.check_tool('osx') - conf.check_tool('python') - -def build(bld): - obj = bld.new_task_gen('cxx', 'shlib', 'node_addon', 'py', 'pyembed', 'pyext') - obj.env['FRAMEWORK'] = 'python' - obj.target = 'binding' - obj.source = "binding.cc" - obj.init_py() - obj.init_pyembed() - -def shutdown(): - # HACK to get binding.node out of build directory. - # better way to do this? - if Options.commands['clean']: - if exists('binding.node'): unlink('binding.node') - else: - if exists('build/default/binding.node') and not exists('binding.node'): - symlink('build/default/binding.node', 'binding.node') From 664621c2dfff672c0aaad89796cd03b84758b1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 22 Jun 2013 03:21:57 -0400 Subject: [PATCH 02/27] better exception handling --- binding.gyp | 22 +++++++++---- package.json | 3 -- binding.cc => src/binding.cc | 15 ++++----- src/utils.cc | 43 +++++++++++++++++++++++++ src/utils.h | 6 ++++ wsgi.js | 62 ------------------------------------ 6 files changed, 72 insertions(+), 79 deletions(-) rename binding.cc => src/binding.cc (97%) create mode 100644 src/utils.cc create mode 100644 src/utils.h delete mode 100644 wsgi.js diff --git a/binding.gyp b/binding.gyp index 20c6278..518c151 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,12 +2,22 @@ "targets": [ { "target_name": "binding", - "sources": [ "binding.cc" ], - "link_settings": { - "libraries": [ - "$(SDKROOT)/System/Library/Frameworks/Python.framework" - ] - } + "sources": [ + "src/binding.cc", + "src/utils.cc" + ], + "conditions": [ + ['OS=="mac"', { + "include_dirs": [ + "$(SDKROOT)/System/Library/Frameworks/Python.framework/Versions/Current/Headers" + ], + "link_settings": { + "libraries": [ + "$(SDKROOT)/System/Library/Frameworks/Python.framework" + ] + } + }] + ] } ] } diff --git a/package.json b/package.json index 911c65f..156d70f 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,6 @@ "version": "0.0.2", "description": "Call python stuff from nodejs", "main": "index.js", - "scripts": { - "install": "node-gyp rebuild" - }, "repository": { "type": "git", "url": "git://github.com/JeanSebTr/node-python.git" diff --git a/binding.cc b/src/binding.cc similarity index 97% rename from binding.cc rename to src/binding.cc index 799274e..fc317ba 100644 --- a/binding.cc +++ b/src/binding.cc @@ -11,10 +11,14 @@ using namespace v8; using std::string; -#include +#include #include + using namespace node; + +#include "utils.h" + class PyObjectWrapper : public ObjectWrap { PyObject* mPyObject; public: @@ -235,12 +239,7 @@ class PyObjectWrapper : public ObjectWrap { PyObject* result = PyObject_CallObject(mPyObject, args_tuple); Py_XDECREF(args_tuple); if(!result) { - PyErr_Clear(); - return ThrowException( - Exception::Error( - String::New("Python exception") - ) - ); + return ThrowPythonException(); } else { RETURN_NEW_PYOBJ(scope, result); } @@ -272,4 +271,4 @@ init (Handle target) { Py_Initialize(); PyObjectWrapper::Initialize(target); } - +NODE_MODULE(binding, init) diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 0000000..f4a8274 --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,43 @@ + +#include +#include + +#include "utils.h" + +Handle ThrowPythonException() { + PyObject *ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + // maybe useless to protect against bad use of ThrowPythonException ? + if(!ptype) { + 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 err; + if(PyErr_GivenExceptionMatches(ptype, PyExc_ReferenceError)) { + err = Exception::ReferenceError(msg); + } + else if(PyErr_GivenExceptionMatches(ptype, PyExc_SyntaxError)) { + err = Exception::SyntaxError(msg); + } + else if(PyErr_GivenExceptionMatches(ptype, PyExc_TypeError)) { + err = Exception::TypeError(msg); + } + else { + err = Exception::Error(msg); + } + + // @TODO : handle stacktrace + + Py_XDECREF(ptype); + Py_XDECREF(pvalue); + Py_XDECREF(ptraceback); + + return ThrowException(err); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..34139e3 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,6 @@ + +#include + +using namespace v8; + +Handle ThrowPythonException(); diff --git a/wsgi.js b/wsgi.js deleted file mode 100644 index 1a2a2cb..0000000 --- a/wsgi.js +++ /dev/null @@ -1,62 +0,0 @@ -var sys = require('sys'), - puts = sys.puts, - binding = require('./binding'), - path_additions = require('./path_additions'), - http = require('http'), - url = require('url'), - stdin = process.openStdin(); - -var sys = binding.import('sys'); -var os = binding.import('os'); - -os.environ.update({ - 'DJANGO_SETTINGS_MODULE':'project.development', -}); - -/* -var gary_busey = binding.import("gary_busey"); -var result = gary_busey.say_hey("man i suck"); -*/ -var django_wsgi = binding.import('django.core.handlers.wsgi'); - -var wsgi_handler = django_wsgi.WSGIHandler() -wsgi_handler.load_middleware(); - -var server = http.createServer(function (req, res) { - var path_and_query = url.parse(req.url); - if(!path_and_query.pathname.match(/^\/media/)) { - var wsgi_request = django_wsgi.WSGIRequest({ - 'PATH_INFO':path_and_query.pathname, - 'QUERY_STRING':path_and_query.query, - 'HTTP_VERSION':req.httpVersion, - 'HTTP_ACCEPT':req.headers['http-accept'], - 'HTTP_ACCEPT_CHARSET':req.headers['http-accept-charset'], - 'HTTP_ACCEPT_ENCODING':req.headers['http-accept-encoding'], - 'HTTP_ACCEPT_LANGUAGE':req.headers['http-accept-language'], - 'HTTP_CACHE_CONTROL':req.headers['http-cache-control'], - 'REQUEST_METHOD':req.method, - 'HTTP_HOST':req.headers['http-host'] - }); - var response = wsgi_handler.get_response(wsgi_request), - headers = response._headers.valueOf(), - content = response.content.toString(), - headers_out = {}, - status_code = response.status_code.valueOf(); - - for(var i in headers) { - var as_array = headers[i].valueOf(); - headers_out[as_array[0]] = as_array[1].toString(); - }; - res.writeHead(status_code, headers_out); - res.write(content); - res.end(); - } else { - res.writeHead(200, {"Content-Type":"text/html"}); - res.write("

sorry, no images.

"); - res.end(); - } -}).listen(8000); -process.addListener('SIGINT', function () { - server.close(); - puts("Shutting down..."); -}); From b74958d67c11f9a819c7ad3c81510f3aa0604910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sun, 23 Jun 2013 01:23:23 -0400 Subject: [PATCH 03/27] better type conversion --- binding.gyp | 3 +- src/binding.cc | 294 +++++---------------------------------- src/py_object_wrapper.cc | 79 +++++++++++ src/py_object_wrapper.h | 219 +++++++++++++++++++++++++++++ 4 files changed, 337 insertions(+), 258 deletions(-) create mode 100644 src/py_object_wrapper.cc create mode 100644 src/py_object_wrapper.h diff --git a/binding.gyp b/binding.gyp index 518c151..3e02080 100644 --- a/binding.gyp +++ b/binding.gyp @@ -4,7 +4,8 @@ "target_name": "binding", "sources": [ "src/binding.cc", - "src/utils.cc" + "src/utils.cc", + "src/py_object_wrapper.cc" ], "conditions": [ ['OS=="mac"', { diff --git a/src/binding.cc b/src/binding.cc index fc317ba..65190bf 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,274 +1,54 @@ // binding.cc -#include + #include #include -#define RETURN_NEW_PYOBJ(scope,pyobject) \ - Local jsobject = python_function_template_->GetFunction()->NewInstance(); \ - PyObjectWrapper* py_object_wrapper = new PyObjectWrapper(pyobject); \ - py_object_wrapper->Wrap(jsobject);\ - return scope.Close(jsobject); - -using namespace v8; -using std::string; +#include #include -#include - -using namespace node; - +#include "py_object_wrapper.h" #include "utils.h" -class PyObjectWrapper : public ObjectWrap { - PyObject* mPyObject; - public: - static Persistent python_function_template_; - PyObjectWrapper(PyObject* obj) : mPyObject(obj), ObjectWrap() { } - virtual ~PyObjectWrapper() { - Py_XDECREF(mPyObject); - mPyObject = NULL; - } - - static void - Initialize(Handle target) { - HandleScope scope; - Local fn_tpl = FunctionTemplate::New(); - Local obj_tpl = fn_tpl->InstanceTemplate(); - - obj_tpl->SetInternalFieldCount(1); - - // this has first priority. see if the properties already exist on the python object - obj_tpl->SetNamedPropertyHandler(Get, Set); - - // If we're calling `toString`, delegate to our version of ToString - obj_tpl->SetAccessor(String::NewSymbol("toString"), ToStringAccessor); - - // likewise for valueOf - obj_tpl->SetAccessor(String::NewSymbol("valueOf"), ValueOfAccessor); - - // Python objects can be called as functions. - obj_tpl->SetCallAsFunctionHandler(Call, Handle()); - - python_function_template_ = Persistent::New(fn_tpl); - // let's also export "import" - Local import = FunctionTemplate::New(Import); - target->Set(String::New("import"), import->GetFunction()); - }; - - static Handle - Get(Local key, const AccessorInfo& info) { - // returning an empty Handle object signals V8 that we didn't - // find the property here, and we should check the "NamedAccessor" functions - HandleScope scope; - PyObjectWrapper* wrapper = ObjectWrap::Unwrap(info.Holder()); - String::Utf8Value utf8_key(key); - string value(*utf8_key); - PyObject* result = wrapper->InstanceGet(value); - if(result) { - RETURN_NEW_PYOBJ(scope, result); - } - return Handle(); - } - - static Handle - Set(Local key, Local value, const AccessorInfo& info) { - // we don't know what to do. - return Undefined(); - }; - - static Handle - CallAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(Call); - return scope.Close(func->GetFunction()); - }; - - static Handle - ToStringAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(ToString); - return scope.Close(func->GetFunction()); - }; - - static Handle - ValueOfAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(ValueOf); - return scope.Close(func->GetFunction()); - } - - static Handle - Call(const Arguments& args) { - HandleScope scope; - Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - Handle result = pyobjwrap->InstanceCall(args); - return scope.Close(result); - } - - static Handle - ToString(const Arguments& args) { - HandleScope scope; - Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - Local result = String::New(pyobjwrap->InstanceToString(args).c_str()); - return scope.Close(result); - } - static Handle ValueOf(const Handle& obj) { - }; - - static Handle - ValueOf(const Arguments& args) { - HandleScope scope; - Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - PyObject* py_obj = pyobjwrap->InstanceGetPyObject(); - if(PyCallable_Check(py_obj)) { - Local call = FunctionTemplate::New(Call); - return scope.Close(call->GetFunction()); - } else if (PyNumber_Check(py_obj)) { - long long_result = PyLong_AsLong(py_obj); - return scope.Close(Integer::New(long_result)); - } else if (PySequence_Check(py_obj)) { - int len = PySequence_Length(py_obj); - Local array = Array::New(len); - for(int i = 0; i < len; ++i) { - Handle jsobj = python_function_template_->GetFunction()->NewInstance(); - PyObject* py_obj_out = PySequence_GetItem(py_obj, i); - PyObjectWrapper* obj_out = new PyObjectWrapper(py_obj_out); - obj_out->Wrap(jsobj); - array->Set(i, jsobj); - } - return scope.Close(array); - } else if (PyMapping_Check(py_obj)) { - int len = PyMapping_Length(py_obj); - Local object = Object::New(); - PyObject* keys = PyMapping_Keys(py_obj); - PyObject* values = PyMapping_Values(py_obj); - for(int i = 0; i < len; ++i) { - PyObject *key = PySequence_GetItem(keys, i), - *value = PySequence_GetItem(values, i), - *key_as_string = PyObject_Str(key); - char* cstr = PyString_AsString(key_as_string); - - Local jsobj = python_function_template_->GetFunction()->NewInstance(); - PyObjectWrapper* obj_out = new PyObjectWrapper(value); - obj_out->Wrap(jsobj); - Py_XDECREF(key); - Py_XDECREF(key_as_string); - } - Py_XDECREF(keys); - Py_XDECREF(values); - return scope.Close(object); - } - return Undefined(); - } - - static Handle - Import(const Arguments& args) { - HandleScope scope; - 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); - Py_XDECREF(module_name); +using namespace v8; +using namespace node; +using std::string; - PyObjectWrapper* pyobject_wrapper = new PyObjectWrapper(module); - RETURN_NEW_PYOBJ(scope, module); - } +Handle import(const Arguments& args) { + HandleScope scope; + 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) { + return ThrowPythonException(); + } + Py_XDECREF(module_name); + + return scope.Close(PyObjectWrapper::New(module)); +} - static PyObject* - ConvertToPython(const Handle& value) { - int len; - HandleScope scope; - if(value->IsString()) { - return PyString_FromString(*String::Utf8Value(value->ToString())); - } else if(value->IsNumber()) { - return PyFloat_FromDouble(value->NumberValue()); - } else if(value->IsObject()) { - Local obj = value->ToObject(); - if(!obj->FindInstanceInPrototypeChain(python_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; - } else if(value->IsUndefined()) { - Py_INCREF(Py_None); - return Py_None; - } - return NULL; - } - PyObject* - InstanceGetPyObject() { - return mPyObject; - } +void init (Handle exports) { + HandleScope scope; + Py_Initialize(); - Handle InstanceCall(const Arguments& args) { - // for now, we don't do anything. - 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]); - PyTuple_SET_ITEM(args_tuple, i, py_arg); - } - PyObject* result = PyObject_CallObject(mPyObject, args_tuple); - Py_XDECREF(args_tuple); - if(!result) { - return ThrowPythonException(); - } else { - RETURN_NEW_PYOBJ(scope, result); - } - return Undefined(); - } + PyObjectWrapper::Initialize(); - string InstanceToString(const Arguments& args) { - PyObject* as_string = PyObject_Str(mPyObject); - string native_string(PyString_AsString(as_string)); - Py_XDECREF(as_string); - return native_string; - } + // module.exports.import + exports->Set( + String::NewSymbol("import"), + FunctionTemplate::New(import)->GetFunction() + ); - PyObject* InstanceGet(const string& key) { - if(PyObject_HasAttrString(mPyObject, key.c_str())) { - PyObject* attribute = PyObject_GetAttrString(mPyObject, key.c_str()); - return attribute; - } - return (PyObject*)NULL; - } -}; + // module.exports.PyObject + exports->Set( + String::NewSymbol("PyObject"), + PyObjectWrapper::py_function_template->GetFunction() + ); -Persistent PyObjectWrapper::python_function_template_; -// all v8 plugins must emit -// a "init" function -extern "C" void -init (Handle target) { - HandleScope scope; - Py_Initialize(); - PyObjectWrapper::Initialize(target); } + NODE_MODULE(binding, init) diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc new file mode 100644 index 0000000..6ef394d --- /dev/null +++ b/src/py_object_wrapper.cc @@ -0,0 +1,79 @@ + +#include "py_object_wrapper.h" +#include "utils.h" + +Persistent PyObjectWrapper::py_function_template; + +void PyObjectWrapper::Initialize() { + HandleScope scope; + Local fn_tpl = FunctionTemplate::New(); + Local proto = fn_tpl->PrototypeTemplate(); + Local obj_tpl = fn_tpl->InstanceTemplate(); + + obj_tpl->SetInternalFieldCount(1); + + // this has first priority. see if the properties already exist on the python object + obj_tpl->SetNamedPropertyHandler(Get, Set); + + // If we're calling `toString`, delegate to our version of ToString + proto->SetAccessor(String::NewSymbol("toString"), ToStringAccessor); + + // likewise for valueOf + obj_tpl->SetAccessor(String::NewSymbol("valueOf"), ValueOfAccessor); + + // Python objects can be called as functions. + obj_tpl->SetCallAsFunctionHandler(Call, Handle()); + + py_function_template = Persistent::New(fn_tpl); +} + +Handle PyObjectWrapper::New(PyObject* obj) { + HandleScope scope; + Local jsVal; + + // undefined + if(obj == Py_None) { + jsVal = Local::New(Undefined()); + } + // double + else if(PyFloat_CheckExact(obj)) { + double d = PyFloat_AsDouble(obj); + jsVal = Local::New(Number::New(d)); + } + // integer (can be 64b) + else if(PyInt_CheckExact(obj)) { + long i = PyInt_AsLong(obj); + jsVal = Local::New(Number::New((double) i)); + } + // string + else if(PyString_CheckExact(obj)) { + char *str = PyString_AsString(obj); + if(str) { + jsVal = Local::New(String::New(str)); + } + } + else if(PyBool_Check(obj)) { + int b = PyObject_IsTrue(obj); + if(b != -1) { + jsVal = Local::New(Boolean::New(b)); + } + } + + if(PyErr_Occurred()) { + Py_XDECREF(obj); + return ThrowPythonException(); + } + + if(jsVal.IsEmpty()) { + Local jsObj = py_function_template->GetFunction()->NewInstance(); + PyObjectWrapper* wrapper = new PyObjectWrapper(obj); + wrapper->Wrap(jsObj); + jsVal = Local::New(jsObj); + } + else { + Py_XDECREF(obj); + } + + return scope.Close(jsVal); +} + diff --git a/src/py_object_wrapper.h b/src/py_object_wrapper.h new file mode 100644 index 0000000..5da2c49 --- /dev/null +++ b/src/py_object_wrapper.h @@ -0,0 +1,219 @@ + +#ifndef PY_OBJECT_WRAPPER_H +#define PY_OBJECT_WRAPPER_H + +#include + +#include +#include + +#include "utils.h" + +using namespace v8; +using std::string; + +class PyObjectWrapper : public node::ObjectWrap { + PyObject* mPyObject; + public: + static Persistent py_function_template; + PyObjectWrapper(PyObject* obj) : node::ObjectWrap(), mPyObject(obj) {}; + virtual ~PyObjectWrapper() { + Py_XDECREF(mPyObject); + mPyObject = NULL; + } + + static void Initialize(); + + static Handle New(PyObject* obj); + static Handle New(const Arguments& args); + + static Handle + Get(Local key, const AccessorInfo& info) { + // returning an empty Handle object signals V8 that we didn't + // find the property here, and we should check the "NamedAccessor" functions + HandleScope scope; + PyObjectWrapper* wrapper = ObjectWrap::Unwrap(info.Holder()); + String::Utf8Value utf8_key(key); + string value(*utf8_key); + PyObject* result = wrapper->InstanceGet(value); + if(result) { + return PyObjectWrapper::New(result); + } + return Handle(); + } + + static Handle + Set(Local key, Local value, const AccessorInfo& info) { + // we don't know what to do. + return Undefined(); + }; + + static Handle + CallAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(Call); + return scope.Close(func->GetFunction()); + }; + + static Handle + ToStringAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(ToString); + return scope.Close(func->GetFunction()); + }; + + static Handle + ValueOfAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(ValueOf); + return scope.Close(func->GetFunction()); + } + + static Handle + Call(const Arguments& args) { + HandleScope scope; + //Local this_object = args.This(); + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + Handle result = pyobjwrap->InstanceCall(args); + return scope.Close(result); + } + + static Handle + ToString(const Arguments& args) { + HandleScope scope; + //Local this_object = args.This(); + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + Local result = String::New(pyobjwrap->InstanceToString(args).c_str()); + return scope.Close(result); + } + + static Handle + ValueOf(const Arguments& args) { + HandleScope scope; + //Local this_object = args.This(); + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + PyObject* py_obj = pyobjwrap->InstanceGetPyObject(); + if(PyCallable_Check(py_obj)) { + Local call = FunctionTemplate::New(Call); + return scope.Close(call->GetFunction()); + } else if (PyNumber_Check(py_obj)) { + long long_result = PyLong_AsLong(py_obj); + return scope.Close(Integer::New(long_result)); + } else if (PySequence_Check(py_obj)) { + int len = PySequence_Length(py_obj); + Local array = Array::New(len); + for(int i = 0; i < len; ++i) { + Handle jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); + PyObject* py_obj_out = PySequence_GetItem(py_obj, i); + PyObjectWrapper* obj_out = new PyObjectWrapper(py_obj_out); + obj_out->Wrap(jsobj); + array->Set(i, jsobj); + } + return scope.Close(array); + } else if (PyMapping_Check(py_obj)) { + int len = PyMapping_Length(py_obj); + Local object = Object::New(); + PyObject* keys = PyMapping_Keys(py_obj); + PyObject* values = PyMapping_Values(py_obj); + for(int i = 0; i < len; ++i) { + PyObject *key = PySequence_GetItem(keys, i), + *value = PySequence_GetItem(values, i), + *key_as_string = PyObject_Str(key); + char* cstr = PyString_AsString(key_as_string); + + Local jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); + PyObjectWrapper* obj_out = new PyObjectWrapper(value); + obj_out->Wrap(jsobj); + Py_XDECREF(key); + Py_XDECREF(key_as_string); + } + Py_XDECREF(keys); + Py_XDECREF(values); + return scope.Close(object); + } + return Undefined(); + } + + static PyObject* + ConvertToPython(const Handle& value) { + int len; + HandleScope scope; + if(value->IsString()) { + return PyString_FromString(*String::Utf8Value(value->ToString())); + } 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; + } + 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; + } else if(value->IsUndefined()) { + Py_INCREF(Py_None); + return Py_None; + } + return NULL; + } + + PyObject* + InstanceGetPyObject() { + return mPyObject; + } + + Handle InstanceCall(const Arguments& args) { + // for now, we don't do anything. + 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]); + PyTuple_SET_ITEM(args_tuple, i, py_arg); + } + PyObject* result = PyObject_CallObject(mPyObject, args_tuple); + Py_XDECREF(args_tuple); + if(result) { + return scope.Close(PyObjectWrapper::New(result)); + } else { + return ThrowPythonException(); + } + } + + string InstanceToString(const Arguments& args) { + PyObject* as_string = PyObject_Str(mPyObject); + string native_string(PyString_AsString(as_string)); + Py_XDECREF(as_string); + return native_string; + } + + PyObject* InstanceGet(const string& key) { + if(PyObject_HasAttrString(mPyObject, key.c_str())) { + PyObject* attribute = PyObject_GetAttrString(mPyObject, key.c_str()); + return attribute; + } + return (PyObject*)NULL; + } +}; + + +#endif From 9a173a07fb3df37f5c370753ab79d5040e0b0038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 15:34:30 -0400 Subject: [PATCH 04/27] refactor --- src/binding.cc | 4 - src/py_object_wrapper.cc | 169 +++++++++++++++++++++++++++++++++++ src/py_object_wrapper.h | 188 +++------------------------------------ 3 files changed, 183 insertions(+), 178 deletions(-) diff --git a/src/binding.cc b/src/binding.cc index 65190bf..cae0aab 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,7 +1,3 @@ -// binding.cc - -#include -#include #include #include diff --git a/src/py_object_wrapper.cc b/src/py_object_wrapper.cc index 6ef394d..370b391 100644 --- a/src/py_object_wrapper.cc +++ b/src/py_object_wrapper.cc @@ -47,6 +47,7 @@ Handle PyObjectWrapper::New(PyObject* obj) { } // string else if(PyString_CheckExact(obj)) { + // ref to internal representation: no need to dealloc char *str = PyString_AsString(obj); if(str) { jsVal = Local::New(String::New(str)); @@ -77,3 +78,171 @@ Handle PyObjectWrapper::New(PyObject* obj) { return scope.Close(jsVal); } +Handle PyObjectWrapper::Get(Local key, const AccessorInfo& info) { + // returning an empty Handle object signals V8 that we didn't + // find the property here, and we should check the "NamedAccessor" functions + HandleScope scope; + PyObjectWrapper* wrapper = ObjectWrap::Unwrap(info.Holder()); + String::Utf8Value utf8_key(key); + string value(*utf8_key); + PyObject* result = wrapper->InstanceGet(value); + if(result) { + return PyObjectWrapper::New(result); + } + return Handle(); +} + +Handle PyObjectWrapper::Set(Local key, Local value, const AccessorInfo& info) { + // we don't know what to do. + return Undefined(); +} + +Handle PyObjectWrapper::CallAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(Call); + return scope.Close(func->GetFunction()); +} + +Handle PyObjectWrapper::ToStringAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(ToString); + return scope.Close(func->GetFunction()); +} + +Handle PyObjectWrapper::ValueOfAccessor(Local property, const AccessorInfo& info) { + HandleScope scope; + Local func = FunctionTemplate::New(ValueOf); + return scope.Close(func->GetFunction()); +} + +Handle PyObjectWrapper::Call(const Arguments& args) { + HandleScope scope; + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + Handle result = pyobjwrap->InstanceCall(args); + return scope.Close(result); +} + +Handle PyObjectWrapper::ToString(const Arguments& args) { + HandleScope scope; + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + Local result = String::New(pyobjwrap->InstanceToString(args).c_str()); + return scope.Close(result); +} + +Handle PyObjectWrapper::ValueOf(const Arguments& args) { + HandleScope scope; + PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); + PyObject* py_obj = pyobjwrap->InstanceGetPyObject(); + if(PyCallable_Check(py_obj)) { + Local call = FunctionTemplate::New(Call); + return scope.Close(call->GetFunction()); + } else if (PyNumber_Check(py_obj)) { + long long_result = PyLong_AsLong(py_obj); + return scope.Close(Integer::New(long_result)); + } else if (PySequence_Check(py_obj)) { + int len = PySequence_Length(py_obj); + Local array = Array::New(len); + for(int i = 0; i < len; ++i) { + Handle jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); + PyObject* py_obj_out = PySequence_GetItem(py_obj, i); + PyObjectWrapper* obj_out = new PyObjectWrapper(py_obj_out); + obj_out->Wrap(jsobj); + array->Set(i, jsobj); + } + return scope.Close(array); + } else if (PyMapping_Check(py_obj)) { + int len = PyMapping_Length(py_obj); + Local object = Object::New(); + PyObject* keys = PyMapping_Keys(py_obj); + PyObject* values = PyMapping_Values(py_obj); + for(int i = 0; i < len; ++i) { + PyObject *key = PySequence_GetItem(keys, i), + *value = PySequence_GetItem(values, i), + *key_as_string = PyObject_Str(key); + char* cstr = PyString_AsString(key_as_string); + + Local jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); + PyObjectWrapper* obj_out = new PyObjectWrapper(value); + obj_out->Wrap(jsobj); + Py_XDECREF(key); + Py_XDECREF(key_as_string); + } + Py_XDECREF(keys); + Py_XDECREF(values); + return scope.Close(object); + } + return Undefined(); +} + +PyObject* PyObjectWrapper::ConvertToPython(const Handle& value) { + int len; + HandleScope scope; + if(value->IsString()) { + return PyString_FromString(*String::Utf8Value(value->ToString())); + } 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; + } + 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; + } else if(value->IsUndefined()) { + Py_RETURN_NONE; + } + return NULL; +} + +Handle PyObjectWrapper::InstanceCall(const Arguments& args) { + // for now, we don't do anything. + 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]); + PyTuple_SET_ITEM(args_tuple, i, py_arg); + } + PyObject* result = PyObject_CallObject(mPyObject, args_tuple); + Py_XDECREF(args_tuple); + if(result) { + return scope.Close(PyObjectWrapper::New(result)); + } else { + return ThrowPythonException(); + } +} + +string PyObjectWrapper::InstanceToString(const Arguments& args) { + PyObject* as_string = PyObject_Str(mPyObject); + string native_string(PyString_AsString(as_string)); + Py_XDECREF(as_string); + return native_string; +} + +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/py_object_wrapper.h b/src/py_object_wrapper.h index 5da2c49..9020fa0 100644 --- a/src/py_object_wrapper.h +++ b/src/py_object_wrapper.h @@ -27,192 +27,32 @@ class PyObjectWrapper : public node::ObjectWrap { static Handle New(PyObject* obj); static Handle New(const Arguments& args); - static Handle - Get(Local key, const AccessorInfo& info) { - // returning an empty Handle object signals V8 that we didn't - // find the property here, and we should check the "NamedAccessor" functions - HandleScope scope; - PyObjectWrapper* wrapper = ObjectWrap::Unwrap(info.Holder()); - String::Utf8Value utf8_key(key); - string value(*utf8_key); - PyObject* result = wrapper->InstanceGet(value); - if(result) { - return PyObjectWrapper::New(result); - } - return Handle(); - } + static Handle Get(Local key, const AccessorInfo& info); + static Handle Set(Local key, Local value, const AccessorInfo& info); - static Handle - Set(Local key, Local value, const AccessorInfo& info) { - // we don't know what to do. - return Undefined(); - }; + static Handle CallAccessor(Local property, const AccessorInfo& info); - static Handle - CallAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(Call); - return scope.Close(func->GetFunction()); - }; + static Handle ToStringAccessor(Local property, const AccessorInfo& info); - static Handle - ToStringAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(ToString); - return scope.Close(func->GetFunction()); - }; + static Handle ValueOfAccessor(Local property, const AccessorInfo& info); - static Handle - ValueOfAccessor(Local property, const AccessorInfo& info) { - HandleScope scope; - Local func = FunctionTemplate::New(ValueOf); - return scope.Close(func->GetFunction()); - } + static Handle Call(const Arguments& args); - static Handle - Call(const Arguments& args) { - HandleScope scope; - //Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - Handle result = pyobjwrap->InstanceCall(args); - return scope.Close(result); - } + static Handle ToString(const Arguments& args); - static Handle - ToString(const Arguments& args) { - HandleScope scope; - //Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - Local result = String::New(pyobjwrap->InstanceToString(args).c_str()); - return scope.Close(result); - } + static Handle ValueOf(const Arguments& args); - static Handle - ValueOf(const Arguments& args) { - HandleScope scope; - //Local this_object = args.This(); - PyObjectWrapper* pyobjwrap = ObjectWrap::Unwrap(args.This()); - PyObject* py_obj = pyobjwrap->InstanceGetPyObject(); - if(PyCallable_Check(py_obj)) { - Local call = FunctionTemplate::New(Call); - return scope.Close(call->GetFunction()); - } else if (PyNumber_Check(py_obj)) { - long long_result = PyLong_AsLong(py_obj); - return scope.Close(Integer::New(long_result)); - } else if (PySequence_Check(py_obj)) { - int len = PySequence_Length(py_obj); - Local array = Array::New(len); - for(int i = 0; i < len; ++i) { - Handle jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); - PyObject* py_obj_out = PySequence_GetItem(py_obj, i); - PyObjectWrapper* obj_out = new PyObjectWrapper(py_obj_out); - obj_out->Wrap(jsobj); - array->Set(i, jsobj); - } - return scope.Close(array); - } else if (PyMapping_Check(py_obj)) { - int len = PyMapping_Length(py_obj); - Local object = Object::New(); - PyObject* keys = PyMapping_Keys(py_obj); - PyObject* values = PyMapping_Values(py_obj); - for(int i = 0; i < len; ++i) { - PyObject *key = PySequence_GetItem(keys, i), - *value = PySequence_GetItem(values, i), - *key_as_string = PyObject_Str(key); - char* cstr = PyString_AsString(key_as_string); - - Local jsobj = PyObjectWrapper::py_function_template->GetFunction()->NewInstance(); - PyObjectWrapper* obj_out = new PyObjectWrapper(value); - obj_out->Wrap(jsobj); - Py_XDECREF(key); - Py_XDECREF(key_as_string); - } - Py_XDECREF(keys); - Py_XDECREF(values); - return scope.Close(object); - } - return Undefined(); - } + static PyObject* ConvertToPython(const Handle& value); - static PyObject* - ConvertToPython(const Handle& value) { - int len; - HandleScope scope; - if(value->IsString()) { - return PyString_FromString(*String::Utf8Value(value->ToString())); - } 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; - } - 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; - } else if(value->IsUndefined()) { - Py_INCREF(Py_None); - return Py_None; - } - return NULL; - } - - PyObject* - InstanceGetPyObject() { + PyObject* InstanceGetPyObject() { return mPyObject; - } + }; - Handle InstanceCall(const Arguments& args) { - // for now, we don't do anything. - 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]); - PyTuple_SET_ITEM(args_tuple, i, py_arg); - } - PyObject* result = PyObject_CallObject(mPyObject, args_tuple); - Py_XDECREF(args_tuple); - if(result) { - return scope.Close(PyObjectWrapper::New(result)); - } else { - return ThrowPythonException(); - } - } + Handle InstanceCall(const Arguments& args); - string InstanceToString(const Arguments& args) { - PyObject* as_string = PyObject_Str(mPyObject); - string native_string(PyString_AsString(as_string)); - Py_XDECREF(as_string); - return native_string; - } + string InstanceToString(const Arguments& args); - PyObject* InstanceGet(const string& key) { - if(PyObject_HasAttrString(mPyObject, key.c_str())) { - PyObject* attribute = PyObject_GetAttrString(mPyObject, key.c_str()); - return attribute; - } - return (PyObject*)NULL; - } + PyObject* InstanceGet(const string& key); }; From 3339a465346c760e80444f16b53c30b3b75594aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 17:02:58 -0400 Subject: [PATCH 05/27] use of python-config to get proper build flags --- binding.gyp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/binding.gyp b/binding.gyp index 3e02080..418e53c 100644 --- a/binding.gyp +++ b/binding.gyp @@ -9,14 +9,21 @@ ], "conditions": [ ['OS=="mac"', { - "include_dirs": [ - "$(SDKROOT)/System/Library/Frameworks/Python.framework/Versions/Current/Headers" + "xcode_settings": { + "OTHER_CFLAGS": [ + " Date: Sat, 6 Jul 2013 17:30:48 -0400 Subject: [PATCH 06/27] fix linking on linux --- binding.gyp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding.gyp b/binding.gyp index 418e53c..ebeaa63 100644 --- a/binding.gyp +++ b/binding.gyp @@ -21,8 +21,8 @@ "cflags": [ " Date: Sat, 6 Jul 2013 17:31:09 -0400 Subject: [PATCH 07/27] add travis-ci --- .travis.yml | 6 ++++++ README.md | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c0c2b9b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js + node_js: + - "0.11" + - "0.10" + - "0.8" + - "0.6" \ No newline at end of file diff --git a/README.md b/README.md index 719122d..f1dfbab 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ python bridge for nodejs! +[![Build Status](https://travis-ci.org/[JeanSebTr]/[node-python].png)](https://travis-ci.org/[JeanSebTr]/[node-python]) + Forked from [chrisdickinson/node-python](https://github.com/chrisdickinson/node-python) and updated to use node-gyp ## Installation From 02528d779434abd8fd70728fa3f16960df1b42f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 17:36:00 -0400 Subject: [PATCH 08/27] fix yml syntax --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c0c2b9b..c2cb8ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js - node_js: +node_js: - "0.11" - "0.10" - "0.8" From 56b5c24b476cbfb7dd6e76473c95bf990f7c606c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 17:38:01 -0400 Subject: [PATCH 09/27] fix travis-ci img --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1dfbab..e0d86af 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ python bridge for nodejs! -[![Build Status](https://travis-ci.org/[JeanSebTr]/[node-python].png)](https://travis-ci.org/[JeanSebTr]/[node-python]) +[![Build Status](https://travis-ci.org/JeanSebTr/node-python.png)](https://travis-ci.org/JeanSebTr/node-python) Forked from [chrisdickinson/node-python](https://github.com/chrisdickinson/node-python) and updated to use node-gyp From f4b36d4570bc12c367f04407421093e7650c02e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 17:47:24 -0400 Subject: [PATCH 10/27] add dumb test & release 0.0.3 --- package.json | 6 +++++- test/linker.js | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 test/linker.js diff --git a/package.json b/package.json index 156d70f..82a8367 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "node-python", - "version": "0.0.2", + "version": "0.0.3", "description": "Call python stuff from nodejs", "main": "index.js", "repository": { "type": "git", "url": "git://github.com/JeanSebTr/node-python.git" }, + "bugs": "https://github.com/JeanSebTr/node-python/issues", "keywords": [ "python", "bridge" @@ -22,6 +23,9 @@ "email": "chris@neversaw.us" } ], + "scripts": { + "test": "node test/linker.js" + }, "license": "BSD", "gypfile": true, "readmeFilename": "README.md" diff --git a/test/linker.js b/test/linker.js new file mode 100644 index 0000000..a8f5aed --- /dev/null +++ b/test/linker.js @@ -0,0 +1,2 @@ + +require('../index.js'); From 7f517dfa64d49c506db671ed4ad373508a56e5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Sat, 6 Jul 2013 18:44:27 -0400 Subject: [PATCH 11/27] update README [skip ci] --- README.md | 71 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index e0d86af..453cca9 100644 --- a/README.md +++ b/README.md @@ -2,51 +2,62 @@ python bridge for nodejs! -[![Build Status](https://travis-ci.org/JeanSebTr/node-python.png)](https://travis-ci.org/JeanSebTr/node-python) +Hyper-beta, don't hesitate to try and report bugs! -Forked from [chrisdickinson/node-python](https://github.com/chrisdickinson/node-python) and updated to use node-gyp +[![Build Status](https://travis-ci.org/JeanSebTr/node-python.png)](https://travis-ci.org/JeanSebTr/node-python) ## Installation ```npm install node-python``` -Tested on OSX 10.7.4 with node 0.8.15 - ## Usage -[I](https://github.com/chrisdickinson) bumped this up from "playground" to "binding" on account of it starting to feel like the -right thing to do. +```javascript + +// python stuff +var python = require('node-python'); +var os = python.import('os'); + +// nodejs stuff +var path = require('path'); + +assert(os.path.basename(os.getcwd()) == path.basename(process.cwd())) + +``` + +You should now go have fun with that and make it brokes :) + +## Current status + +What should work: -This is a binding between Node.js and Python; unfortunately as written it actually embeds a -python process inside of Node. It's of extremely alpha quality and was originally written with -the intent of getting a better understanding of the internals of both V8 and CPython. +* Conversion between None and Undefined +* Conversion between Python's and Node's Boolean +* Conversion between Python's and Node's String +* Calling python functions from node +* Conversion from Python's Array to Node's Array -But, yeah, okay. So the cool things: +What may be broken: - var sys = require('sys'); - var python = require('./binding'); - var pysys = python.import('sys'); - sys.puts(pysys.toString()); +* Losing precision from Python's 64 bits Integer to Node's Number -Will output python's `sys.path`. And passing in arguments works, too: +What's to be done: - var python = require('./binding'), - os = python.import('os'), - cwd = os.getcwd(), - basename = os.path.basename(cwd); +* Conversion from Node's Array to Python's Array +* Pass javascript object to python +* Call javascript function from python - var sys = require('sys'); +What would be realy awesome: - sys.puts(basename.toString()); +* Proper object introspection -Unfortunately Python objects are not really fully translated into native Javascript objects yet; -you have to cast them from whatever they are into whatever you want them to be. At the moment, the -only provided cast is "toString", but that should change in the near future (hopefully). -Passing python objects that you get from calling python functions from javascript can seamlessly -be passed back into python functions (no casting required). Currently there's what [I](https://github.com/chrisdickinson) assume to be -a passable argument translation implementation for simple Objects (ones that act like dicts), -Arrays, Numbers (maybe?), and Strings. +## History -You can slap together a tiny WSGI hosting thing on it, as well, which is provided in `wsgi.js`. -It's half implemented, but it's midnight on a Sunday and [I](https://github.com/chrisdickinson) should probably sleep. +* **v0.0.3** : 2013-07-06 + - Refactor + - Better type conversion & error handling + - Compilation now properly working on both OSX and Linux. Windows compilation _may_ work too +* **v0.0.2** : 2012-12-21 + - Forked from [chrisdickinson/node-python](https://github.com/chrisdickinson/node-python) + - Compilation with node-gyp From ff1950bfacd2a15cc07a1499d01bf2577ce6957d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Wed, 9 Oct 2013 14:40:22 -0400 Subject: [PATCH 12/27] use bindings module --- README.md | 3 +++ index.js | 2 +- package.json | 7 +++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 453cca9..887f4a7 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ What should work: What may be broken: * Losing precision from Python's 64 bits Integer to Node's Number +* If you're using node v0.6.x (please upgrade) you'll have to manually compile with node-gyp What's to be done: @@ -54,6 +55,8 @@ What would be realy awesome: ## History +* **v0.0.4** : 2013-10-09 + - use the bindings module to load the native extension * **v0.0.3** : 2013-07-06 - Refactor - Better type conversion & error handling diff --git a/index.js b/index.js index a88cdf1..e205f21 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,3 @@ -module.exports = require('./build/Release/binding.node'); +module.exports = require('bindings')('binding.node') diff --git a/package.json b/package.json index 82a8367..06f0bad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-python", - "version": "0.0.3", + "version": "0.0.4", "description": "Call python stuff from nodejs", "main": "index.js", "repository": { @@ -28,5 +28,8 @@ }, "license": "BSD", "gypfile": true, - "readmeFilename": "README.md" + "readmeFilename": "README.md", + "dependencies": { + "bindings": "~1.1.1" + } } From f090cb1e7966bd787f0779e3796470e7a5f9af41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Tremblay?= Date: Thu, 31 Jul 2014 22:07:24 -0400 Subject: [PATCH 13/27] specify cpython's dependency for ubuntu/debian --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 887f4a7..349fb24 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ Hyper-beta, don't hesitate to try and report bugs! ## Installation +For the module to compile, you need cpython's development headers. +Example for Ubuntu : run ```sudo apt-get install python-dev``` + +Then simply: ```npm install node-python``` ## Usage From 46a0ed02409b5607724a5df44c6e83723a0b5d87 Mon Sep 17 00:00:00 2001 From: Matt Walters Date: Wed, 6 Aug 2014 17:55:09 -0400 Subject: [PATCH 14/27] 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 15/27] 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 16/27] 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 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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 27/27] 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": {