├── .gitignore ├── LICENSE ├── README.md ├── appveyor.yml ├── binding.gyp ├── circle.yml ├── index.js ├── nodeaio ├── nodeaio.js └── nodeaio.py ├── package.json ├── src ├── common.h ├── debug.h ├── error.cc ├── error.h ├── gil-lock.cc ├── gil-lock.h ├── jsobject.cc ├── jsobject.h ├── pyjs.cc ├── pyjsfunction.cc ├── pyjsfunction.h ├── pymodule.cc ├── pymodule.h ├── python-util.h ├── typeconv.cc └── typeconv.h └── test ├── __init__.py ├── builtins.js ├── common.js ├── error.js ├── import.js ├── jsobject.js ├── madness.js ├── multithread.js ├── node-python-index.js └── support ├── __init__.py ├── test.py └── test2.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | __pycache__/ 4 | *.pyc 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jianfeng Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyjs 2 | [![CircleCI](https://circleci.com/gh/swordfeng/pyjs/tree/master.svg?style=shield)](https://circleci.com/gh/swordfeng/pyjs/tree/master) 3 | [![AppVeyor](https://ci.appveyor.com/api/projects/status/ms9u389g3i3kwpjd/branch/master?svg=true)](https://ci.appveyor.com/project/swordfeng/pyjs/branch/master) 4 | Pyjs - Call Python code from Node.js in process 5 | 6 | ### TODO 7 | + Error handling for Javascript functions called in Python 8 | + Check Python functions' result for each time 9 | + Add assertions in native functions 10 | + Make convenient ways to do python add, sub, ... expressions in javascript 11 | + Resolve circular reference between Python and JavaScript (may be difficult!) 12 | + Fix python read from stdin in node repl (get EOF error currently) - maybe use asyncio? 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 3 | NODE_VERSION: 6 4 | 5 | install: 6 | - ps: Install-Product node $env:NODE_VERSION 7 | - set PATH=C:\Python36;%PATH% 8 | - npm install --python=C:\Python27\python.exe 9 | 10 | test_script: 11 | - python --version 12 | - node --version 13 | - npm --version 14 | - npm test 15 | 16 | build: off 17 | 18 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "pyjs-native", 5 | "sources": [ 6 | "src/pyjs.cc", 7 | "src/jsobject.cc", 8 | "src/typeconv.cc", 9 | "src/gil-lock.cc", 10 | "src/pyjsfunction.cc", 11 | "src/pymodule.cc", 12 | "src/error.cc" 13 | ], 14 | "include_dirs": [ 15 | " nodeaio.stop()); 17 | 18 | module.exports = { aioco }; 19 | -------------------------------------------------------------------------------- /nodeaio/nodeaio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import asyncio 3 | import threading 4 | loop = asyncio.get_event_loop() 5 | class LoopThread(threading.Thread): 6 | def __init__(self): 7 | super(LoopThread, self).__init__() 8 | self.finalizing = False 9 | def run(self): 10 | asyncio.set_event_loop(loop) 11 | loop.run_forever() 12 | pending = asyncio.Task.all_tasks() 13 | loop.run_until_complete(asyncio.gather(*pending)) 14 | 15 | class Thenable: 16 | def __init__(self, coro): 17 | self.resolve_handlers = [] 18 | self.reject_handlers = [] 19 | self.done = False 20 | self.result = None 21 | self.exception = None 22 | self.coro = coro 23 | def then(self, resolve, reject): 24 | if self.done: 25 | if self.exception != None: 26 | reject(self.exception) 27 | else: 28 | resolve(self.result) 29 | else: 30 | self.resolve_handlers.append(resolve) 31 | self.reject_handlers.append(reject) 32 | async def run(self): 33 | try: 34 | self.result = await self.coro 35 | except BaseException as e: 36 | self.exception = e 37 | self.done = True 38 | # should have no exceptions thrown from node.js? 39 | if self.exception != None: 40 | for handler in self.reject_handlers: 41 | handler(self.exception) 42 | else: 43 | for handler in self.resolve_handlers: 44 | handler(self.result) 45 | # in case of circular reference 46 | del self.resolve_handlers 47 | del self.reject_handlers 48 | 49 | LoopThread().start() 50 | 51 | def ensure_coroutine(coro): 52 | promise = Thenable(coro) 53 | loop.call_soon_threadsafe(asyncio.ensure_future, promise.run()) 54 | return promise 55 | def stop(): 56 | loop.call_soon_threadsafe(loop.stop) 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyjs", 3 | "version": "0.1.2", 4 | "description": "Call Python code from Node.js side, in the same process", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node-gyp build && mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/swordfeng/pyjs.git" 12 | }, 13 | "author": "swordfeng", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/swordfeng/pyjs/issues" 17 | }, 18 | "homepage": "https://github.com/swordfeng/pyjs#readme", 19 | "dependencies": { 20 | "bindings": "^1.2.1", 21 | "nan": "^2.6.2" 22 | }, 23 | "devDependencies": { 24 | "chai": "^4.0.1", 25 | "mocha": "^3.4.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #ifdef COMPILER 6 | #undef COMPILER 7 | 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #define ASSERT(cond) \ 10 | do { \ 11 | if(!(cond)) { \ 12 | fflush(stdout); \ 13 | fprintf(stderr, "\33[1;31m"); \ 14 | fprintf(stderr, "%s:%d: %s: Assertion `%s' failed.\n", __FILE__, __LINE__, __func__, #cond ); \ 15 | fprintf(stderr, "\33[0m\n"); \ 16 | exit(1); \ 17 | } \ 18 | } while(0) 19 | 20 | #ifdef DEBUG 21 | #define LOG(...) \ 22 | do { \ 23 | fflush(stdout); \ 24 | fprintf(stderr, "\33[1;32m"); \ 25 | fprintf(stderr, __VA_ARGS__); \ 26 | fprintf(stderr, "\33[0m\n"); \ 27 | } while (0) 28 | #else 29 | #define LOG(...) 30 | #endif 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /src/error.cc: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "typeconv.h" 7 | #include "debug.h" 8 | 9 | v8::Local makeJsErrorObject() { 10 | PyObject *ptype, *pvalue, *ptraceback; 11 | PyErr_Fetch(&ptype, &pvalue, &ptraceback); 12 | PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); 13 | ASSERT(ptype); 14 | PyObjectWithRef type(ptype), value(pvalue), traceback(ptraceback); 15 | 16 | std::ostringstream stackStream; 17 | 18 | // name and message 19 | PyObjectWithRef errName(PyObject_GetAttrString(type, "__name__")); 20 | PyObjectWithRef errMessage(PyObject_Str(value)); 21 | stackStream << PyUnicode_AsUTF8(errName) << ": " << PyUnicode_AsUTF8(errMessage) << std::endl; 22 | 23 | // python stack 24 | std::vector frames; 25 | PyTracebackObject *last = nullptr, *tb = reinterpret_cast(traceback.borrow()); 26 | while (tb != nullptr) { 27 | last = tb; 28 | std::ostringstream frameStringStream; 29 | frameStringStream << PyUnicode_AsUTF8(tb->tb_frame->f_code->co_name) 30 | << " (" << PyUnicode_AsUTF8(tb->tb_frame->f_code->co_filename) 31 | << ":" << tb->tb_lineno << ")"; 32 | frames.push_back(frameStringStream.str()); 33 | tb = tb->tb_next; 34 | } 35 | 36 | if (last) { 37 | // print line 38 | PyObjectWithRef linecache(PyImport_ImportModule("linecache")); 39 | PyObjectWithRef getline(PyObject_GetAttrString(linecache, "getline")); 40 | PyObjectWithRef args(PyTuple_New(2)); 41 | PyTuple_SetItem(args, 0, PyObjectMakeRef(last->tb_frame->f_code->co_filename).escape()); 42 | PyTuple_SetItem(args, 1, PyLong_FromLong(last->tb_lineno)); 43 | PyObjectWithRef line(PyObject_CallObject(getline, args)); 44 | const char *lineCString = PyUnicode_AsUTF8(line); 45 | if (*lineCString != 0) stackStream << " >>" << lineCString; 46 | } 47 | 48 | for (auto it = frames.rbegin(); it != frames.rend(); it++) { 49 | stackStream << " at " << *it << std::endl; 50 | } 51 | 52 | stackStream << " ==== FFI Boundary ====" << std::endl; 53 | 54 | // js stack 55 | Nan::EscapableHandleScope scope; 56 | v8::Local o = Nan::New(); 57 | v8::Local jsError = Nan::GetCurrentContext()->Global() 58 | ->Get(Nan::New("Error").ToLocalChecked())->ToObject(); 59 | v8::Local captureStackTrace = jsError 60 | ->Get(Nan::New("captureStackTrace").ToLocalChecked()).As(); 61 | const int argc = 1; 62 | v8::Local argv[argc] = { o }; 63 | captureStackTrace->Call(Nan::Undefined(), argc, argv); 64 | std::string jsStackString(*Nan::Utf8String(o->Get(Nan::New("stack").ToLocalChecked()))); 65 | size_t firstNewLine = jsStackString.find('\n'); 66 | if (firstNewLine != std::string::npos && firstNewLine != jsStackString.size()) { 67 | stackStream << jsStackString.substr(firstNewLine + 1); 68 | } 69 | 70 | // make final Error object 71 | o->Set(Nan::New("stack").ToLocalChecked(), Nan::New(stackStream.str().c_str()).ToLocalChecked()); 72 | o->Set(Nan::New("name").ToLocalChecked(), PyToJs(errName)); 73 | o->Set(Nan::New("message").ToLocalChecked(), PyToJs(errMessage)); 74 | Nan::SetPrototype(o, jsError->Get(Nan::New("prototype").ToLocalChecked())); // TODO: .ToChecked(); ? 75 | ASSERT(!PyErr_Occurred()); 76 | return scope.Escape(o); 77 | } 78 | 79 | void makePyError(Nan::TryCatch &trycatch) { 80 | //v8::Local message = trycatch.Message()->Get(); 81 | //LOG("JS: %s\n", *Nan::Utf8String(message)); 82 | v8::Local e = trycatch.Exception(); 83 | if (e->IsObject()) { 84 | v8::Local exc = e->ToObject(); 85 | v8::Local name = exc->Get(Nan::New("name").ToLocalChecked()); 86 | v8::Local message = exc->Get(Nan::New("message").ToLocalChecked()); 87 | v8::Local stack = exc->Get(Nan::New("stack").ToLocalChecked()); 88 | if (name->IsString()) { 89 | LOG("name: %s\n", *Nan::Utf8String(name)); 90 | } 91 | if (message->IsString()) { 92 | LOG("message: %s\n", *Nan::Utf8String(message)); 93 | } 94 | if (stack->IsString()) { 95 | LOG("stack: %s\n", *Nan::Utf8String(stack)); 96 | } 97 | LOG("current:\n"); 98 | PyErr_SetString(PyExc_Exception, *Nan::Utf8String(stack)); 99 | } else { 100 | PyErr_SetString(PyExc_Exception, *Nan::Utf8String(e->ToString())); 101 | } 102 | trycatch.Reset(); 103 | } 104 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | // May return! 5 | #define CHECK_PYTHON_ERROR \ 6 | if (PyErr_Occurred()) { \ 7 | return Nan::ThrowError(makeJsErrorObject()); \ 8 | } 9 | 10 | v8::Local makeJsErrorObject(); 11 | void makePyError(Nan::TryCatch &trycatch); 12 | -------------------------------------------------------------------------------- /src/gil-lock.cc: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "gil-lock.h" 3 | #include 4 | #include 5 | #include "debug.h" 6 | 7 | namespace GILLock { 8 | void Init() { 9 | PyEval_InitThreads(); 10 | static PyThreadState *_save = nullptr; 11 | 12 | static uv_prepare_t gilrelease; 13 | static uv_check_t gilensure; 14 | 15 | uv_prepare_init(uv_default_loop(), &gilrelease); 16 | uv_check_init(uv_default_loop(), &gilensure); 17 | uv_prepare_start(&gilrelease, [] (uv_prepare_t *) { 18 | _save = PyEval_SaveThread(); 19 | }); 20 | uv_check_start(&gilensure, [] (uv_check_t *) { 21 | if (PyGILState_Check()) return; 22 | ASSERT(_save); 23 | PyEval_RestoreThread(_save); 24 | }); 25 | uv_unref((uv_handle_t *)&gilrelease); 26 | uv_unref((uv_handle_t *)&gilensure); 27 | } 28 | } // namespace GILLock 29 | -------------------------------------------------------------------------------- /src/gil-lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GILLock { 4 | void Init(); 5 | } // namespace GILLock 6 | -------------------------------------------------------------------------------- /src/jsobject.cc: -------------------------------------------------------------------------------- 1 | #include "jsobject.h" 2 | #include "typeconv.h" 3 | #include 4 | #include "error.h" 5 | #include "debug.h" 6 | 7 | Nan::Persistent JsPyWrapper::constructorTpl; 8 | Nan::Persistent JsPyWrapper::callableTpl; 9 | 10 | bool JsPyWrapper::implicitConversionEnabled = true; 11 | 12 | void JsPyWrapper::SetObject(PyObjectWithRef object, v8::Local instance) { 13 | this->object = std::move(object); 14 | } 15 | 16 | PyObjectWithRef JsPyWrapper::GetObject() { 17 | return object; 18 | } 19 | 20 | JsPyWrapper *JsPyWrapper::UnWrap(v8::Local object) { 21 | Nan::HandleScope scope; 22 | // find real `this` 23 | while (!IsInstance(object)) { 24 | v8::Local prototypeValue = object->GetPrototype(); 25 | if (prototypeValue->IsNull()) { 26 | return nullptr; // error 27 | } 28 | object = prototypeValue->ToObject(); 29 | } 30 | return ObjectWrap::Unwrap(object); 31 | } 32 | 33 | v8::Local JsPyWrapper::NewInstance(PyObjectWithRef object) { 34 | Nan::EscapableHandleScope scope; 35 | v8::Local cons = Nan::New(constructorTpl)->GetFunction(); 36 | v8::Local instance = Nan::NewInstance(cons, 0, {}).ToLocalChecked(); 37 | JsPyWrapper *wrapper = UnWrap(instance); 38 | wrapper->SetObject(object, instance); 39 | return scope.Escape(instance); 40 | } 41 | 42 | bool JsPyWrapper::IsInstance(v8::Local object) { 43 | Nan::HandleScope scope; 44 | return Nan::New(constructorTpl)->HasInstance(object); 45 | } 46 | 47 | v8::Local JsPyWrapper::makeFunction(v8::Local instance) { 48 | Nan::EscapableHandleScope scope; 49 | v8::Local ctpl = Nan::New(callableTpl); 50 | v8::Local callable = ctpl->NewInstance(); 51 | Nan::SetPrototype(callable, instance); // TODO: .ToChecked(); ? 52 | return scope.Escape(callable); 53 | } 54 | 55 | void JsPyWrapper::Init(v8::Local exports) { 56 | GILStateHolder gilholder; 57 | Nan::HandleScope scope; 58 | // Prepare constructor template 59 | v8::Local tpl = Nan::New(New); 60 | tpl->SetClassName(Nan::New("PyObject").ToLocalChecked()); 61 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 62 | // Prototype 63 | v8::Local prototpl = tpl->PrototypeTemplate(); 64 | Nan::SetMethod(prototpl, "$repr", Repr); 65 | Nan::SetMethod(prototpl, "$str", Str); 66 | Nan::SetMethod(prototpl, "$call", Call); 67 | Nan::SetMethod(prototpl, "$value", Value); 68 | Nan::SetMethod(prototpl, "$attr", Attr); 69 | Nan::SetMethod(prototpl, "$", Attr); 70 | Nan::SetMethod(prototpl, "$type", Type); 71 | 72 | Nan::SetMethod(prototpl, "toString", Str); 73 | Nan::SetMethod(prototpl, "inspect", Repr); 74 | Nan::SetMethod(prototpl, "valueOf", ValueOf); 75 | 76 | Nan::SetNamedPropertyHandler(tpl->InstanceTemplate(), AttrGetter, AttrSetter, 0, 0, AttrEnumerator); 77 | 78 | constructorTpl.Reset(tpl); 79 | exports->Set(Nan::New("PyObject").ToLocalChecked(), tpl->GetFunction()); 80 | 81 | v8::Local ctpl = Nan::New(); 82 | Nan::SetNamedPropertyHandler(ctpl, AttrGetter, AttrSetter, 0, 0, AttrEnumerator); 83 | Nan::SetCallAsFunctionHandler(ctpl, CallFunction); 84 | callableTpl.Reset(ctpl); 85 | 86 | Nan::SetAccessor(exports, Nan::New("implicitConversion").ToLocalChecked(), 87 | [] (v8::Local, const Nan::PropertyCallbackInfo &args) { 88 | args.GetReturnValue().Set(Nan::New(implicitConversionEnabled)); 89 | }, [] (v8::Local, v8::Local value, const Nan::PropertyCallbackInfo &args) { 90 | implicitConversionEnabled = value->BooleanValue(); 91 | } 92 | ); 93 | } 94 | 95 | void JsPyWrapper::New(const Nan::FunctionCallbackInfo &args) { 96 | GILStateHolder gilholder; 97 | Nan::HandleScope scope; 98 | v8::Local thisObject; 99 | if (!args.IsConstructCall()) { 100 | v8::Local cons = Nan::New(constructorTpl)->GetFunction(); 101 | thisObject = Nan::NewInstance(cons).ToLocalChecked(); 102 | } else { 103 | thisObject = args.This(); 104 | } 105 | JsPyWrapper *wrapper = new JsPyWrapper(); 106 | wrapper->Wrap(thisObject); 107 | if (args.Length() >= 1) { 108 | wrapper->SetObject(JsToPy(args[0]), thisObject); 109 | } else { 110 | wrapper->SetObject(JsToPy(Nan::Undefined()), thisObject); 111 | } 112 | args.GetReturnValue().Set(thisObject); 113 | CHECK_PYTHON_ERROR; 114 | } 115 | 116 | void JsPyWrapper::Value(const Nan::FunctionCallbackInfo &args) { 117 | GILStateHolder gilholder; 118 | JsPyWrapper *wrapper = UnWrap(args.This()); 119 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 120 | 121 | args.GetReturnValue().Set(PyToJs(wrapper->object)); 122 | CHECK_PYTHON_ERROR; 123 | } 124 | 125 | void JsPyWrapper::Repr(const Nan::FunctionCallbackInfo &args) { 126 | GILStateHolder gilholder; 127 | JsPyWrapper *wrapper = UnWrap(args.This()); 128 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 129 | 130 | args.GetReturnValue().Set(PyToJs(PyObjectWithRef(PyObject_Repr(wrapper->object)))); 131 | CHECK_PYTHON_ERROR; 132 | } 133 | 134 | void JsPyWrapper::Str(const Nan::FunctionCallbackInfo &args) { 135 | GILStateHolder gilholder; 136 | JsPyWrapper *wrapper = UnWrap(args.This()); 137 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 138 | 139 | PyObjectWithRef object = PyObjectMakeRef(wrapper->object); 140 | if (!PyUnicode_Check(object)) object = PyObjectWithRef(PyObject_Str(wrapper->object)); 141 | args.GetReturnValue().Set(PyToJs(object)); 142 | CHECK_PYTHON_ERROR; 143 | } 144 | 145 | void JsPyWrapper::ValueOf(const Nan::FunctionCallbackInfo &args) { 146 | GILStateHolder gilholder; 147 | JsPyWrapper *wrapper = UnWrap(args.This()); 148 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 149 | 150 | PyObjectBorrowed object = wrapper->object; 151 | if (PyLong_CheckExact(object)) { 152 | args.GetReturnValue().Set(Nan::New(PyLong_AsDouble(object))); 153 | } else { 154 | args.GetReturnValue().Set(PyToJs(object)); 155 | } 156 | CHECK_PYTHON_ERROR; 157 | } 158 | 159 | void JsPyWrapper::Type(const Nan::FunctionCallbackInfo &args) { 160 | GILStateHolder gilholder; 161 | JsPyWrapper *wrapper = UnWrap(args.This()); 162 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 163 | 164 | PyObjectBorrowed object = wrapper->object; 165 | args.GetReturnValue().Set(PyToJs(PyObject_Type(object))); 166 | CHECK_PYTHON_ERROR; 167 | } 168 | 169 | void JsPyWrapper::Attr(const Nan::FunctionCallbackInfo &args) { 170 | GILStateHolder gilholder; 171 | JsPyWrapper *wrapper = UnWrap(args.This()); 172 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 173 | 174 | PyObjectBorrowed object = wrapper->object; 175 | Nan::HandleScope scope; 176 | 177 | PyObjectWithRef attr(JsToPy(args[0])); 178 | if (args.Length() == 1) { 179 | PyObjectWithRef subObject(PyObject_GetAttr(object, attr)); 180 | if (subObject) { 181 | args.GetReturnValue().Set(PyToJs(subObject, implicitConversionEnabled)); 182 | } else { 183 | args.GetReturnValue().Set(Nan::Undefined()); 184 | } 185 | } else if (args.Length() == 2) { 186 | PyObject_SetAttr(object, attr, JsToPy(args[1])); 187 | } 188 | CHECK_PYTHON_ERROR; 189 | } 190 | 191 | void JsPyWrapper::AttrGetter(v8::Local name, const Nan::PropertyCallbackInfo &info) { 192 | GILStateHolder gilholder; 193 | JsPyWrapper *wrapper = UnWrap(info.This()); 194 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 195 | 196 | if (!name->IsString()) return; 197 | PyObjectBorrowed object = wrapper->object; 198 | PyObjectWithRef attr = JsToPy(name); 199 | if (!PyObject_HasAttr(object, attr)) return; 200 | PyObjectWithRef subObject(PyObject_GetAttr(object, attr)); 201 | info.GetReturnValue().Set(PyToJs(subObject, implicitConversionEnabled)); 202 | CHECK_PYTHON_ERROR; 203 | } 204 | 205 | void JsPyWrapper::AttrSetter(v8::Local name, v8::Local value, 206 | const Nan::PropertyCallbackInfo &info) { 207 | GILStateHolder gilholder; 208 | JsPyWrapper *wrapper = UnWrap(info.This()); 209 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 210 | 211 | if (!name->IsString()) return; 212 | if (!IsInstance(info.This())) return; 213 | 214 | PyObject_SetAttr(wrapper->object, JsToPy(name), JsToPy(value)); 215 | CHECK_PYTHON_ERROR; 216 | } 217 | 218 | void JsPyWrapper::AttrEnumerator(const Nan::PropertyCallbackInfo &info) { 219 | GILStateHolder gilholder; 220 | JsPyWrapper *wrapper = UnWrap(info.This()); 221 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 222 | 223 | if (!IsInstance(info.This())) return; 224 | 225 | PyObjectWithRef pyAttrs(PyObject_Dir(wrapper->object)); 226 | info.GetReturnValue().Set(PyToJs(pyAttrs).As()); 227 | CHECK_PYTHON_ERROR; 228 | } 229 | 230 | void JsPyWrapper::Call(const Nan::FunctionCallbackInfo &args) { 231 | GILStateHolder gilholder; 232 | JsPyWrapper *wrapper = UnWrap(args.This()); 233 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 234 | 235 | PyObjectBorrowed pyFunc = wrapper->object; 236 | if (!PyCallable_Check(pyFunc)) { 237 | return Nan::ThrowTypeError("not a function"); 238 | } 239 | Nan::HandleScope scope; 240 | PyObjectWithRef pyArgs(PyTuple_New(0)); 241 | PyObjectWithRef pyKw(nullptr); 242 | 243 | if (args[0]->IsArray()) { 244 | v8::Local jsArgs = args[0].As(); 245 | pyArgs = PyObjectWithRef(PyTuple_New(jsArgs->Length())); 246 | for (ssize_t i = 0; i < jsArgs->Length(); i++) { 247 | int result = PyTuple_SetItem(pyArgs, i, JsToPy(jsArgs->Get(ssize_cast(i))).escape()); 248 | ASSERT(result != -1); 249 | } 250 | if (args[1]->IsObject()) { 251 | v8::Local jsKw = args[1]->ToObject(); 252 | pyKw = PyObjectWithRef(PyDict_New()); 253 | v8::Local keys = Nan::GetOwnPropertyNames(jsKw).ToLocalChecked(); 254 | for (ssize_t i = 0; i < keys->Length(); i++) { 255 | v8::Local jsKey = keys->Get(ssize_cast(i)); 256 | v8::Local jsValue = jsKw->Get(jsKey); 257 | int result = PyDict_SetItem(pyKw, JsToPy(jsKey), JsToPy(jsValue)); 258 | ASSERT(result != -1); 259 | } 260 | } 261 | } 262 | args.GetReturnValue().Set(PyToJs(PyObjectWithRef(PyObject_Call(pyFunc, pyArgs, pyKw)), implicitConversionEnabled)); 263 | CHECK_PYTHON_ERROR; 264 | } 265 | 266 | void JsPyWrapper::CallFunction(const Nan::FunctionCallbackInfo &args) { 267 | GILStateHolder gilholder; 268 | JsPyWrapper *wrapper = UnWrap(args.This()); 269 | if (!wrapper || !wrapper->object) return Nan::ThrowTypeError("Unexpected object"); 270 | 271 | PyObjectBorrowed pyFunc = wrapper->object; 272 | if (!PyCallable_Check(pyFunc)) { 273 | return Nan::ThrowTypeError("not a function"); 274 | } 275 | Nan::HandleScope scope; 276 | // arguments 277 | PyObjectWithRef pyTuple(PyTuple_New(args.Length())); 278 | for (ssize_t i = 0; i < args.Length(); i++) { 279 | int result = PyTuple_SetItem(pyTuple, i, JsToPy(args[ssize_cast(i)]).escape()); 280 | ASSERT(result != -1); 281 | } 282 | args.GetReturnValue().Set(PyToJs(PyObjectWithRef(PyObject_CallObject(pyFunc, pyTuple)), implicitConversionEnabled)); 283 | CHECK_PYTHON_ERROR; 284 | } 285 | -------------------------------------------------------------------------------- /src/jsobject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "python-util.h" 4 | 5 | class JsPyWrapper : public Nan::ObjectWrap { 6 | public: 7 | static void Init(v8::Local exports); 8 | static v8::Local NewInstance(PyObjectWithRef object); 9 | static bool IsInstance(v8::Local object); 10 | static JsPyWrapper *UnWrap(v8::Local object); 11 | 12 | static bool implicitConversionEnabled; 13 | 14 | void SetObject(PyObjectWithRef object, v8::Local instance); 15 | PyObjectWithRef GetObject(); 16 | 17 | static v8::Local makeFunction(v8::Local instance); 18 | private: 19 | static void New(const Nan::FunctionCallbackInfo &args); 20 | 21 | static void Repr(const Nan::FunctionCallbackInfo &args); 22 | static void Str(const Nan::FunctionCallbackInfo &args); 23 | 24 | static void ValueOf(const Nan::FunctionCallbackInfo &args); 25 | 26 | static void Value(const Nan::FunctionCallbackInfo &args); 27 | static void Attr(const Nan::FunctionCallbackInfo &args); 28 | static void Type(const Nan::FunctionCallbackInfo &args); 29 | 30 | static void Call(const Nan::FunctionCallbackInfo &args); 31 | static void CallFunction(const Nan::FunctionCallbackInfo &args); 32 | 33 | static void AttrGetter(v8::Local name, const Nan::PropertyCallbackInfo &info); 34 | static void AttrSetter(v8::Local name, v8::Local value, const Nan::PropertyCallbackInfo &info); 35 | static void AttrEnumerator(const Nan::PropertyCallbackInfo &info); 36 | 37 | static Nan::Persistent constructorTpl; 38 | static Nan::Persistent callableTpl; 39 | 40 | PyObjectWithRef object; 41 | }; 42 | -------------------------------------------------------------------------------- /src/pyjs.cc: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #ifdef LINUX 5 | #include 6 | #endif 7 | 8 | #include "jsobject.h" 9 | #include "typeconv.h" 10 | #include "python-util.h" 11 | #include "error.h" 12 | #include "gil-lock.h" 13 | #include "debug.h" 14 | #include "pymodule.h" 15 | 16 | class AtExit { 17 | public: 18 | AtExit(std::function atExit): atExit(atExit) {} 19 | ~AtExit() { atExit(); } 20 | private: 21 | std::function atExit; 22 | }; 23 | 24 | void Builtins(v8::Local name, const Nan::PropertyCallbackInfo &args) { 25 | GILStateHolder gilholder; 26 | Nan::HandleScope scope; 27 | PyObjectWithRef object(PyImport_ImportModule("builtins")); 28 | args.GetReturnValue().Set(JsPyWrapper::NewInstance(object)); 29 | } 30 | 31 | void Import(const Nan::FunctionCallbackInfo &args) { 32 | GILStateHolder gilholder; 33 | Nan::HandleScope scope; 34 | if (args.Length() == 0 || !args[0]->IsString()) return Nan::ThrowTypeError("invalid module name"); 35 | PyObjectWithRef object(PyImport_ImportModule(*static_cast(args[0]->ToString()))); 36 | CHECK_PYTHON_ERROR; 37 | args.GetReturnValue().Set(JsPyWrapper::NewInstance(object)); 38 | } 39 | 40 | void Eval(const Nan::FunctionCallbackInfo &args) { 41 | GILStateHolder gilholder; 42 | Nan::HandleScope scope; 43 | if (args.Length() == 0 || !args[0]->IsString()) return Nan::ThrowTypeError("invalid Python code"); 44 | 45 | PyObjectWithRef global(PyDict_New()), local(PyDict_New()); 46 | PyObjectWithRef object(PyRun_String(*Nan::Utf8String(args[0]), Py_eval_input, global, local)); 47 | CHECK_PYTHON_ERROR; 48 | args.GetReturnValue().Set(PyToJs(object, JsPyWrapper::implicitConversionEnabled)); 49 | } 50 | 51 | void Module(v8::Local name, const Nan::PropertyCallbackInfo &args) { 52 | GILStateHolder gilholder; 53 | Nan::HandleScope scope; 54 | PyObjectWithRef object = PyObjectMakeRef(JsPyModule::GetModule()); 55 | args.GetReturnValue().Set(JsPyWrapper::NewInstance(object)); 56 | } 57 | 58 | void Init(v8::Local exports) { 59 | 60 | // find process.argv first 61 | v8::Local jsArgv = Nan::GetCurrentContext()->Global() 62 | ->Get(Nan::New("process").ToLocalChecked())->ToObject() 63 | ->Get(Nan::New("argv").ToLocalChecked()).As(); 64 | 65 | if (jsArgv->Length() > 0) { 66 | //wchar_t *name = Py_DecodeLocale(*Nan::Utf8String(jsArgv->Get(0)->ToString()), nullptr); 67 | PyObject *pyname(PyUnicode_DecodeLocale(*Nan::Utf8String(jsArgv->Get(0)->ToString()), nullptr)); 68 | wchar_t *name = PyUnicode_AsUnicode(pyname); 69 | Py_SetProgramName(name); 70 | //PyMem_RawFree(name); 71 | } 72 | 73 | // python initialize 74 | void *python_lib; 75 | #ifdef LINUX 76 | python_lib = dlopen(PYTHON_LIB, RTLD_LAZY | RTLD_GLOBAL); 77 | #endif 78 | Py_InitializeEx(0); 79 | // not working? 80 | //node::AtExit([] (void *) { Py_Finalize(); std::cout << "exit" << std::endl; }); 81 | static AtExit exitHandler([python_lib] { 82 | if (!PyGILState_Check()) PyGILState_Ensure(); 83 | Py_Finalize(); 84 | #ifdef LINUX 85 | dlclose(python_lib); 86 | #endif 87 | }); 88 | GILLock::Init(); 89 | TypeConvInit(); 90 | JsPyWrapper::Init(exports); 91 | 92 | // init sys.argv 93 | int argc = jsArgv->Length(); 94 | argc && --argc; 95 | std::unique_ptr argv(new wchar_t *[argc]); 96 | std::vector pyargv; 97 | for (int i = 0; i < argc; i++) { 98 | PyObjectWithRef a(PyUnicode_DecodeLocale(*Nan::Utf8String(jsArgv->Get(i + 1)->ToString()), nullptr)); 99 | //argv[i] = Py_DecodeLocale(*Nan::Utf8String(jsArgv->Get(i + 1)->ToString()), nullptr); 100 | argv[i] = PyUnicode_AsUnicode(a.borrow()); 101 | pyargv.push_back(a); 102 | } 103 | PySys_SetArgv(argc, argv.get()); 104 | for (int i = 0; i < argc; i++) { 105 | //PyMem_RawFree(argv[i]); 106 | pyargv.clear(); 107 | } 108 | 109 | // py module init 110 | JsPyModule::Init(); 111 | 112 | Nan::SetAccessor(exports, Nan::New("module").ToLocalChecked(), Module); 113 | Nan::SetAccessor(exports, Nan::New("builtins").ToLocalChecked(), Builtins); 114 | Nan::SetMethod(exports, "import", Import); 115 | Nan::SetMethod(exports, "eval", Eval); 116 | } 117 | 118 | NODE_MODULE(pyjs, Init) 119 | -------------------------------------------------------------------------------- /src/pyjsfunction.cc: -------------------------------------------------------------------------------- 1 | #include "pyjsfunction.h" 2 | #include "typeconv.h" 3 | #include "debug.h" 4 | #include "error.h" 5 | #include 6 | 7 | namespace JsPyModule { 8 | 9 | static size_t functionRefCount = 0; 10 | static uv_async_t functionRefChanged; 11 | static uv_async_t functionHandle; 12 | static uv_mutex_t functionHandleLock; 13 | static uv_mutex_t functionCallLock; 14 | static uv_cond_t functionCallCond; 15 | 16 | static PyThreadState *mainThread; 17 | 18 | void performFunction(JsFunction *self) { 19 | Nan::HandleScope scope; 20 | ssize_t argc = PyTuple_Size(self->obj); 21 | std::vector> argv(argc); 22 | for (ssize_t i = 0; i < argc; i++) { 23 | argv[i] = PyToJs(PyTuple_GetItem(self->obj, i)); 24 | } 25 | v8::Local func = Nan::New(self->savedFunction); 26 | Nan::TryCatch trycatch; 27 | v8::Local result = func->Call(Nan::Undefined(), ssize_cast(argc), argv.data()); 28 | if (trycatch.HasCaught()) { 29 | makePyError(trycatch); 30 | self->obj = nullptr; 31 | } else self->obj = JsToPy(result).escape(); 32 | } 33 | 34 | void functionCallCallback(uv_async_t *async) { 35 | JsFunction *self = (JsFunction *) async->data; 36 | uv_mutex_lock(&functionCallLock); 37 | performFunction(self); 38 | uv_cond_signal(&functionCallCond); 39 | uv_mutex_unlock(&functionCallLock); 40 | } 41 | 42 | void functionRefChangedCallback(uv_async_t *async) { 43 | LOG("function ref count changed to %lu\n", functionRefCount); 44 | if (functionRefCount == 0) { 45 | uv_unref((uv_handle_t *)&functionHandle); 46 | } else { 47 | uv_ref((uv_handle_t *)&functionHandle); 48 | } 49 | } 50 | 51 | PyObject *JsFunction_NewFunction(v8::Local func) { 52 | JsFunction *self; 53 | self = (JsFunction *)JsFunctionType.tp_alloc(&JsFunctionType, 0); 54 | self->savedFunction.Reset(func); 55 | functionRefCount++; 56 | uv_async_send(&functionRefChanged); 57 | return (PyObject *)self; 58 | } 59 | 60 | v8::Local JsFunction_GetFunction(PyObject *object) { 61 | Nan::EscapableHandleScope scope; 62 | JsFunction *self = (JsFunction *) object; 63 | return scope.Escape(Nan::New(self->savedFunction)); 64 | } 65 | 66 | static void JsFunction_dealloc(JsFunction *self) { 67 | self->savedFunction.Reset(); 68 | functionRefCount--; 69 | uv_async_send(&functionRefChanged); 70 | } 71 | 72 | void JsFunction_SetFunction(JsFunction *self, v8::Local func) { 73 | self->savedFunction.Reset(func); 74 | } 75 | 76 | static PyObject *JsFunction_call(PyObject *obj, PyObject *args, PyObject *kw) { 77 | JsFunction *self = (JsFunction *)obj; 78 | uv_mutex_lock(&functionHandleLock); 79 | self->obj = args; 80 | if (PyThreadState_Get() == mainThread) { 81 | performFunction(self); 82 | } else { 83 | uv_mutex_lock(&functionCallLock); 84 | functionHandle.data = self; 85 | uv_async_send(&functionHandle); 86 | Py_BEGIN_ALLOW_THREADS 87 | uv_cond_wait(&functionCallCond, &functionCallLock); 88 | Py_END_ALLOW_THREADS 89 | uv_mutex_unlock(&functionCallLock); 90 | } 91 | uv_mutex_unlock(&functionHandleLock); 92 | // deal with returned value 93 | return self->obj; 94 | } 95 | 96 | PyTypeObject JsFunctionType; // static variable inits to 0 97 | 98 | void JsFunction_Init() { 99 | JsFunctionType.ob_base = {PyObject_HEAD_INIT(NULL) 0}; 100 | JsFunctionType.tp_name = "pyjs.JsFunction"; 101 | JsFunctionType.tp_basicsize = sizeof(JsFunction); 102 | JsFunctionType.tp_dealloc = (destructor) JsFunction_dealloc; 103 | JsFunctionType.tp_flags = Py_TPFLAGS_DEFAULT; 104 | JsFunctionType.tp_call = JsFunction_call; 105 | 106 | mainThread = PyThreadState_Get(); 107 | uv_async_init(uv_default_loop(), &functionHandle, functionCallCallback); 108 | uv_async_init(uv_default_loop(), &functionRefChanged, functionRefChangedCallback); 109 | uv_unref((uv_handle_t *)&functionRefChanged); 110 | uv_async_send(&functionRefChanged); 111 | uv_mutex_init(&functionHandleLock); 112 | uv_mutex_init(&functionCallLock); 113 | uv_cond_init(&functionCallCond); 114 | } 115 | 116 | } // namespace JsPyModule 117 | -------------------------------------------------------------------------------- /src/pyjsfunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | #include 5 | 6 | namespace JsPyModule { 7 | typedef struct { 8 | PyObject_HEAD 9 | Nan::Persistent savedFunction; 10 | PyObject *obj; 11 | } JsFunction; 12 | void JsFunction_Init(); 13 | PyObject *JsFunction_NewFunction(v8::Local func); 14 | v8::Local JsFunction_GetFunction(PyObject *object); 15 | void JsFunction_SetFunction(JsFunction *self, v8::Local func); 16 | extern PyTypeObject JsFunctionType; 17 | } // namespace JsPyModule 18 | -------------------------------------------------------------------------------- /src/pymodule.cc: -------------------------------------------------------------------------------- 1 | #include "pymodule.h" 2 | #include 3 | 4 | namespace JsPyModule { 5 | 6 | static PyModuleDef pyjsmodule = { 7 | PyModuleDef_HEAD_INIT, 8 | "pyjs", 9 | "pyjs python module", 10 | -1, 11 | NULL, NULL, NULL, NULL, NULL 12 | }; 13 | static PyObject* module = nullptr; 14 | 15 | PyObject *GetModule(void) { 16 | return module; 17 | } 18 | 19 | void Init() { 20 | JsFunction_Init(); 21 | if (PyType_Ready(&JsFunctionType) < 0) 22 | std::cout << "err!" << std::endl; 23 | 24 | module = PyModule_Create(&pyjsmodule); 25 | 26 | Py_INCREF(&JsFunctionType); 27 | PyModule_AddObject(module, "JsFunction", (PyObject *)&JsFunctionType); 28 | } 29 | 30 | } // namespace JsPyModule 31 | -------------------------------------------------------------------------------- /src/pymodule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pyjsfunction.h" 4 | #include "pymodule.h" 5 | 6 | 7 | namespace JsPyModule { 8 | void Init(); 9 | PyObject *GetModule(void); 10 | } // namespace JsPyModule 11 | -------------------------------------------------------------------------------- /src/python-util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | class GILStateHolder { 5 | public: 6 | GILStateHolder(): gstate(PyGILState_Ensure()) {} 7 | ~GILStateHolder() { PyGILState_Release(gstate); } 8 | private: 9 | PyGILState_STATE gstate; 10 | }; 11 | 12 | typedef PyObject *PyObjectBorrowed; 13 | 14 | class PyObjectWithRef { // PyObject with one reference 15 | public: 16 | PyObjectWithRef(): _object(nullptr) {} 17 | explicit PyObjectWithRef(PyObject * const &object): _object(object) {} // steal one reference 18 | ~PyObjectWithRef() { 19 | GILStateHolder holder; 20 | Py_XDECREF(_object); 21 | } 22 | PyObjectWithRef(const PyObjectWithRef &objWithRef): _object(objWithRef._object) { 23 | Py_XINCREF(_object); 24 | } 25 | PyObjectWithRef(PyObjectWithRef &&objWithRef): _object(objWithRef._object) { 26 | objWithRef._object = nullptr; 27 | } 28 | PyObjectWithRef &operator=(const PyObjectWithRef &objWithRef) { 29 | GILStateHolder holder; 30 | Py_XDECREF(_object); 31 | _object = objWithRef._object; 32 | Py_XINCREF(_object); 33 | return *this; 34 | } 35 | PyObjectWithRef &operator=(PyObjectWithRef &&objWithRef) { 36 | GILStateHolder holder; 37 | Py_XDECREF(_object); 38 | _object = objWithRef._object; 39 | objWithRef._object = nullptr; 40 | return *this; 41 | } 42 | operator PyObjectBorrowed() { // borrow 43 | return _object; 44 | } 45 | PyObject *borrow() { 46 | return _object; 47 | } 48 | PyObject *escape() { // new reference 49 | PyObject *object = _object; 50 | _object = nullptr; 51 | return object; 52 | } 53 | private: 54 | PyObject *_object; 55 | }; 56 | 57 | static inline PyObjectWithRef PyObjectMakeRef(const PyObjectBorrowed &object) { 58 | GILStateHolder gilholder; 59 | Py_XINCREF(object); 60 | return PyObjectWithRef(object); 61 | } 62 | -------------------------------------------------------------------------------- /src/typeconv.cc: -------------------------------------------------------------------------------- 1 | #include "typeconv.h" 2 | #include "jsobject.h" 3 | #include "python-util.h" 4 | #include "pyjsfunction.h" 5 | #include "datetime.h" 6 | #include "debug.h" 7 | 8 | void TypeConvInit() { 9 | PyDateTime_IMPORT; 10 | } 11 | 12 | v8::Local PyToJs(PyObjectBorrowed pyObject, bool implicit) { 13 | Nan::EscapableHandleScope scope; 14 | if (pyObject == nullptr) { 15 | return scope.Escape(Nan::Undefined()); 16 | } 17 | if (implicit) { 18 | if (pyObject == Py_None) { 19 | return scope.Escape(Nan::Null()); 20 | } else if (PyUnicode_CheckExact(pyObject)) { 21 | Py_ssize_t size; 22 | char *str = PyUnicode_AsUTF8AndSize(pyObject, &size); 23 | if (!str) return scope.Escape(Nan::Undefined()); 24 | return scope.Escape(Nan::New(str, ssize_cast(size)).ToLocalChecked()); 25 | } else if (PyBytes_CheckExact(pyObject)) { 26 | char *buf; 27 | Py_ssize_t size; 28 | PyBytes_AsStringAndSize(pyObject, &buf, &size); 29 | return scope.Escape(Nan::CopyBuffer(buf, ssize_cast(size)).ToLocalChecked()); 30 | } else if (PyBool_Check(pyObject)) { 31 | return scope.Escape(Nan::New(pyObject == Py_True)); 32 | } else if (PyFloat_CheckExact(pyObject)) { 33 | return scope.Escape(Nan::New(PyFloat_AsDouble(pyObject))); 34 | } else if (PyList_CheckExact(pyObject)) { 35 | v8::Local jsArr = Nan::New(); 36 | Py_ssize_t size = PyList_Size(pyObject); 37 | ASSERT(size >= 0); 38 | for (ssize_t i = 0; i < size; i++) { 39 | jsArr->Set(ssize_cast(i), PyToJs(PyList_GetItem(pyObject, i))); 40 | } 41 | return scope.Escape(jsArr); 42 | } else if (PyDict_CheckExact(pyObject)) { 43 | v8::Local jsObject = Nan::New(); 44 | PyObject *key, *value; 45 | Py_ssize_t pos = 0; 46 | while (PyDict_Next(pyObject, &pos, &key, &value)) { 47 | jsObject->Set(PyToJs(key), PyToJs(value)); 48 | } 49 | return scope.Escape(jsObject); 50 | } else if (PyObject_Type(pyObject) == (PyObject *)&JsPyModule::JsFunctionType) { 51 | return scope.Escape(JsPyModule::JsFunction_GetFunction(pyObject)); 52 | } else if (PyDateTime_CheckExact(pyObject)) { 53 | PyObjectWithRef pyTsFunc(PyObject_GetAttrString(pyObject, "timestamp")); 54 | PyObjectWithRef pyTs(PyObject_CallObject(pyTsFunc, PyObjectWithRef(PyTuple_New(0)))); 55 | double timestamp = PyFloat_AsDouble(pyTs); 56 | v8::Local jsObject = Nan::New(timestamp * 1000.0).ToLocalChecked(); 57 | return scope.Escape(jsObject); 58 | } 59 | } 60 | if (PyCallable_Check(pyObject)) { 61 | v8::Local jsObject = JsPyWrapper::NewInstance(PyObjectMakeRef(pyObject)); 62 | return scope.Escape(JsPyWrapper::makeFunction(jsObject)); 63 | } else { 64 | return scope.Escape(JsPyWrapper::NewInstance(PyObjectMakeRef(pyObject))); 65 | } 66 | } 67 | 68 | PyObjectWithRef JsToPy(v8::Local jsValue) { 69 | Nan::HandleScope scope; 70 | if (jsValue->IsObject()) { 71 | v8::Local jsObject = jsValue->ToObject(); 72 | JsPyWrapper *object = JsPyWrapper::UnWrap(jsObject); 73 | if (object) { // found wrapper python object 74 | return object->GetObject(); 75 | } 76 | } 77 | if (jsValue->IsNull()) { 78 | return PyObjectMakeRef(Py_None); 79 | } else if (jsValue->IsString()) { 80 | v8::Local jsString = jsValue->ToString(); 81 | return PyObjectWithRef(PyUnicode_FromStringAndSize(*Nan::Utf8String(jsString), jsString->Utf8Length())); 82 | } else if (jsValue->IsTrue()) { 83 | return PyObjectMakeRef(Py_True); 84 | } else if (jsValue->IsFalse()) { 85 | return PyObjectMakeRef(Py_False); 86 | } else if (jsValue->IsNumber()) { 87 | return PyObjectWithRef(PyFloat_FromDouble(jsValue->NumberValue())); 88 | } else if (jsValue->IsArray()) { 89 | v8::Local jsArr = jsValue.As(); 90 | PyObjectWithRef pyArr = PyObjectWithRef(PyList_New(jsArr->Length())); 91 | for (ssize_t i = 0; i < jsArr->Length(); i++) { 92 | int result = PyList_SetItem(pyArr, i, JsToPy(jsArr->Get(ssize_cast(i))).escape()); 93 | ASSERT(result != -1); 94 | } 95 | return pyArr; 96 | } else if (jsValue->IsFunction()) { 97 | return PyObjectWithRef(JsPyModule::JsFunction_NewFunction(jsValue.As())); 98 | } else if (node::Buffer::HasInstance(jsValue)) { // compability? 99 | return PyObjectWithRef(PyBytes_FromStringAndSize(node::Buffer::Data(jsValue), node::Buffer::Length(jsValue))); 100 | } else if (jsValue->IsDate()) { 101 | double timestamp = jsValue.As()->ValueOf() / 1000.0; 102 | PyObjectWithRef args(PyTuple_New(1)); 103 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(timestamp)); 104 | PyObjectWithRef pyDateTime(PyDateTime_FromTimestamp(args)); 105 | return pyDateTime; 106 | } else if (jsValue->IsObject()) { // must be after null, array, function, buffer, date 107 | v8::Local jsObject = jsValue->ToObject(); 108 | PyObjectWithRef pyDict = PyObjectWithRef(PyDict_New()); 109 | v8::Local props = Nan::GetOwnPropertyNames(jsObject).ToLocalChecked(); 110 | for (ssize_t i = 0; i < props->Length(); i++) { 111 | v8::Local jsKey = props->Get(ssize_cast(i)); 112 | v8::Local jsValue = jsObject->Get(jsKey); 113 | int result = PyDict_SetItem(pyDict, JsToPy(jsKey), JsToPy(jsValue)); 114 | ASSERT(result != -1); 115 | } 116 | return pyDict; 117 | } else if (jsValue->IsUndefined()) { 118 | //return PyObjectWithRef(); 119 | return PyObjectMakeRef(Py_None); // avoid some crashes... but this is not expected however, who knows why? 120 | } 121 | ASSERT(0); // should not reach here 122 | } 123 | 124 | v8::Local PyTupleToJsArray(PyObjectBorrowed pyObject) { 125 | Nan::EscapableHandleScope scope; 126 | v8::Local arr = Nan::New(); 127 | ssize_t size = PyTuple_Size(pyObject); 128 | ASSERT(size > 0); 129 | for (ssize_t i = 0; i < size; i++) { 130 | arr->Set(ssize_cast(i), PyToJs(PyTuple_GetItem(pyObject, i))); 131 | } 132 | return scope.Escape(arr); 133 | } 134 | 135 | PyObjectWithRef JsArrayToPyTuple(v8::Local jsValue) { 136 | Nan::HandleScope scope; 137 | v8::Local arr = jsValue.As(); 138 | ssize_t size = arr->Length(); 139 | PyObjectWithRef tup(PyTuple_New(size)); 140 | for (ssize_t i = 0; i < size; i++) { 141 | PyTuple_SetItem(tup, i, JsToPy(arr->Get(ssize_cast(i))).escape()); 142 | } 143 | return tup; 144 | } 145 | -------------------------------------------------------------------------------- /src/typeconv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "common.h" 4 | #include "python-util.h" 5 | //#include "debug.h" 6 | void TypeConvInit(); 7 | v8::Local PyToJs(PyObjectBorrowed pyObject, bool implicit = true); 8 | PyObjectWithRef JsToPy(v8::Local jsValue); 9 | 10 | static inline int ssize_cast(ssize_t size) { 11 | //ASSERT(size >= 0); 12 | //ASSERT(size <= INT_MAX); 13 | return static_cast(size); 14 | } 15 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | a = 5.0 4 | def function1(): 5 | return 'hello' 6 | 7 | def function2(x): 8 | return x * 2 + 1 9 | 10 | def callFunction(func, arg): 11 | return func(arg) 12 | 13 | def waitThenCall(secs, func): 14 | time.sleep(secs) 15 | func() 16 | 17 | def willraise(): 18 | raise TypeError('test') 19 | 20 | 21 | def willraiseUpperframe(n): 22 | if not (n > 0): 23 | willraise() 24 | else: 25 | willraiseUpperframe(n - 1) 26 | 27 | class testclass: 28 | a = 2.0 29 | -------------------------------------------------------------------------------- /test/builtins.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | describe('builtins', function () { 3 | it('len', function () { 4 | assert.equal(2, builtins.len([1,2])); 5 | }); 6 | it('large integer add', function () { 7 | var inta = builtins.int('340282366920938463463374607431768211455'); 8 | var intb = builtins.int(2); 9 | var intc = inta.$('__add__')(intb); 10 | assert.equal('340282366920938463463374607431768211457', intc.toString()); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | global.py = require('../'); 3 | global.assert = require('chai').assert; 4 | global.expect = require('chai').expect; 5 | require('chai').should(); 6 | 7 | global.PyObject = py.PyObject; 8 | global.builtins = py.builtins; 9 | 10 | before(function () { 11 | var sys = py.import('sys'); 12 | if (sys.path.indexOf('') === -1) { 13 | sys.path = [''].concat(sys.path); 14 | 15 | global.testModule = py.import('test'); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /test/error.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | 3 | describe('error handling', function () { 4 | it('throw from python code', function () { 5 | (() => testModule.willraise()).should.throw(/test/); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/import.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | describe('import', function () { 3 | it('import single value', function () { 4 | var test = py.import('test'); 5 | assert.equal(5.0, test.$attr('a')); 6 | }); 7 | it('set value', function () { 8 | var test = py.import('test'); 9 | test.$attr('b', 3); 10 | assert.equal(3, test.$attr('b')); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/jsobject.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | 3 | describe('PyObject', function () { 4 | describe('type conversion', function () { 5 | it('null <---> None', function () { 6 | assert.equal('None', new PyObject(null).$repr()); 7 | assert.equal(null, new PyObject(null).$value()); 8 | }); 9 | it('true <---> True', function () { 10 | assert.equal('True', new PyObject(true).$repr()); 11 | assert.equal(true, new PyObject(true).$value()); 12 | }); 13 | it('false <---> False', function () { 14 | assert.equal('False', new PyObject(false).$repr()); 15 | assert.equal(false, new PyObject(false).$value()); 16 | }); 17 | it('undefined <---> None (just for now)', function () { 18 | assert.equal(PyObject().$value(), null); 19 | }); 20 | it('number <---> double', function () { 21 | assert.equal(15, new PyObject(15).$value()); 22 | assert.equal('15.0', new PyObject(15).$repr()); 23 | }); 24 | it('string <---> unicode', function () { 25 | var crypto = require('crypto'); 26 | var str = crypto.randomBytes(1024).toString('utf8'); 27 | assert.equal(str, new PyObject(str).$value()); 28 | }); 29 | it('array <---> list', function () { 30 | var arr = new PyObject([1, 'aa', false]).$value(); 31 | assert.deepEqual([1, 'aa', false], arr); 32 | }); 33 | it('buffer <---> bytes', function () { 34 | var bytes = new PyObject(new Buffer('abcd', 'utf8')); 35 | assert.equal('b\'abcd\'', bytes.toString()); 36 | var newBuf = bytes.$value(); 37 | assert(newBuf instanceof Buffer); 38 | assert.equal('abcd', newBuf.toString('utf8')); 39 | }); 40 | it('Object <---> dict', function () { 41 | var obj = { 42 | 'a': 1, 43 | 2: 'str', 44 | 'x y': true, 45 | 'something': null 46 | }; 47 | assert.deepEqual(obj, new PyObject(obj).$value()); 48 | }); 49 | it('PyObject <---> object', function () { 50 | var pyobj = new PyObject(15); 51 | pyobj.should.be.instanceof(PyObject); 52 | assert.equal(15, PyObject(pyobj).$value()); 53 | }); 54 | }); 55 | describe('function conversion', function () { 56 | it('should convert python function to javascript function', function() { 57 | (typeof testModule.function1).should.equal('function'); 58 | testModule.function1.should.be.an.instanceof(PyObject); 59 | testModule.function1().should.equal('hello'); 60 | testModule.function2(2).should.equal(5); 61 | testModule.function2.$call([3]).should.equal(7); 62 | }); 63 | it('should convert javascript function to python function', function() { 64 | var pyfunc = PyObject(function (x) { return x + 1; }) 65 | pyfunc.should.be.instanceof(PyObject); 66 | pyfunc.$value().should.be.a('function'); 67 | testModule.callFunction(pyfunc, 1).should.equal(2); 68 | testModule.callFunction(pyfunc, 'a').should.equal('a1'); 69 | }); 70 | }); 71 | describe('prototype', function () { 72 | it('should find the correct wrapper in prototype chain', function () { 73 | var a = PyObject('abc'); 74 | var b = { __proto__: a }; 75 | var c = { __proto__: b }; 76 | b.$value().should.equal('abc'); 77 | c.$value().should.equal('abc'); 78 | }); 79 | it('should throw if nothing is on prototype chain', function () { 80 | var a = PyObject('abc'); 81 | var b = { $value: a.$value }; 82 | (() => b.$value()).should.throw(/Unexpected object/); 83 | }); 84 | }); 85 | describe('getter and setters', function () { 86 | it('getter', function () { 87 | var a = builtins.int(15); 88 | assert.equal(a.bit_length(), 4); 89 | }); 90 | it('setter', function () { 91 | var a = testModule.testclass(); 92 | assert.equal(a.a, 2); 93 | a.a = 4; 94 | assert.equal(a.a, 4); 95 | assert.deepEqual(a.$('__dict__'), { a: 4 }); 96 | }); 97 | }); 98 | describe('valueOf', function () { 99 | it ('should convert int to number', function () { 100 | var a = builtins.int(32); 101 | var b = builtins.float(10); 102 | assert.equal(a + b, 42); 103 | }); 104 | }); 105 | describe('ref count?', function () { 106 | it('repeat wrap', function () { 107 | var a = 'teststring'; 108 | var b = 'teststring'; 109 | for (var i = 0; i < 100; i++) { 110 | a = PyObject(a); 111 | assert.equal(a.$value(), b); 112 | } 113 | }); 114 | it('concat string', function () { 115 | var a = 'teststring'; 116 | var b = 'teststring'; 117 | for (var i = 0; i < 100; i++) { 118 | a = PyObject(a).$('__add__')('b'); 119 | b += 'b'; 120 | assert.equal(a, b); 121 | } 122 | }); 123 | it('no leak', function () { 124 | var a = 'teststring'; 125 | var b = 'teststring'; 126 | for (var i = 0; i < 100000; i++) { 127 | PyObject(a); 128 | } 129 | }); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/madness.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | 3 | describe('madness', function () { 4 | // 5 | }); 6 | -------------------------------------------------------------------------------- /test/multithread.js: -------------------------------------------------------------------------------- 1 | require('./common'); 2 | 3 | describe('multithread using Python\'s threading', function () { 4 | var threading = py.import('threading'); 5 | var time = py.import('time'); 6 | it('should create a thread', function (done) { 7 | this.timeout(3000); 8 | var t = threading.Thread.$call([], 9 | { 10 | target: testModule.waitThenCall, 11 | args: builtins.tuple([2, done]) 12 | } 13 | ); 14 | t.start(); 15 | }); 16 | it('should create a thread and join', function () { 17 | this.timeout(500); 18 | var t = threading.Thread.$call([], { target: time.sleep, args: builtins.tuple([0.3])}); 19 | t.start(); 20 | t.join(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/node-python-index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010, Chris Dickinson 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | // 6 | // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | // Neither the name of the Node-Python binding nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | // 11 | // Original file from https://github.com/JeanSebTr/node-python/blob/master/test/index.js 12 | 13 | require('./common'); 14 | var python = py; 15 | 16 | describe('node-python', function () { 17 | describe('eval', function () { 18 | it('should return resulting value from python statement executed', function () { 19 | var value = python.eval('"1"'); 20 | value.should.equal("1"); 21 | }); 22 | it('should return resulting value from python statement executed, converting to string with complex types', function () { 23 | var decimal = python.import('decimal'); 24 | var smallNum = decimal.Decimal('0.0000000001'); 25 | smallNum.toString().should.equal('1E-10'); 26 | }); 27 | }); 28 | describe('import', function () { 29 | it('should return object representing module imported, containing functions from imported module', function () { 30 | var value = python.import('decimal'); 31 | value.should.have.property('valueOf'); 32 | }); 33 | it('should throw a PythonError when importing a module that does not exist', function () { 34 | expect(function () { 35 | python.import('jibberish'); 36 | }).throw(/No module named \'jibberish\'/); 37 | }); 38 | it('should throw an Error when importing a module that includes bad syntax', function () { 39 | expect(function () { 40 | python.import('test.support.test'); 41 | }).throw(/invalid syntax/) 42 | }); 43 | }); 44 | it('should convert javascript null to python NoneType', function () { 45 | test = python.import('test.support.test2'); 46 | var type = test.getPythonTypeName(null); 47 | type.should.equal('NoneType'); 48 | }); 49 | it('should convert javascript undefined to python NoneType', function () { 50 | test = python.import('test.support.test2'); 51 | var type = test.getPythonTypeName(undefined); 52 | type.should.equal('NoneType'); 53 | }); 54 | it('should convert javascript booleans to python booleans', function () { 55 | test = python.import('test.support.test2'); 56 | var type = test.getPythonTypeName(true); 57 | type.should.equal('bool'); 58 | }); 59 | it('should convert javascript date to python date', function () { 60 | test = python.import('test.support.test2'); 61 | var type = test.getPythonTypeName(new Date()); 62 | type.should.equal('datetime'); 63 | }); 64 | it('should convert javascript numbers to python floats', function () { 65 | test = python.import('test.support.test2'); 66 | var type = test.getPythonTypeName(1); 67 | type.should.equal('float'); 68 | }); 69 | it('should convert javascript arrays to python list', function () { 70 | test = python.import('test.support.test2'); 71 | var type = test.getPythonTypeName([]); 72 | type.should.equal('list'); 73 | }); 74 | it('should convert javascript objects to python dictionaries', function () { 75 | test = python.import('test.support.test2'); 76 | var type = test.getPythonTypeName({}); 77 | type.should.equal('dict'); 78 | }); 79 | it('should convert javascript nested objects correctly', function () { 80 | test = python.import('test.support.test2'); 81 | var type = test.getPythonTypeName2({ 82 | value: 1 83 | }, 'value'); 84 | type.should.equal('float'); 85 | var type = test.getPythonTypeName2({ 86 | value: true 87 | }, 'value'); 88 | type.should.equal('bool'); 89 | var type = test.getPythonTypeName2({ 90 | value: new Date() 91 | }, 'value'); 92 | type.should.equal('datetime'); 93 | var type = test.getPythonTypeName2({ 94 | value: {} 95 | }, 'value'); 96 | type.should.equal('dict'); 97 | var type = test.getPythonTypeName2({ 98 | value: ['one', 'two', 'three'] 99 | }, 'value'); 100 | type.should.equal('list'); 101 | var i = 0, arr = []; 102 | while (i < 10000) { 103 | arr.push(Math.random().toString()) 104 | i++; 105 | } 106 | var type = test.getPythonTypeName(arr); 107 | type.should.equal('list'); 108 | }); 109 | it('should convert python dicts to javascript objects', function () { 110 | test = python.import('test.support.test2'); 111 | var value = test.getPythonValue({ 112 | value: 1 113 | }); 114 | value.should.have.property('value', 1); 115 | }); 116 | it('should convert python lists to javascript arrays', function () { 117 | test = python.import('test.support.test2'); 118 | var value = test.getPythonValue([ 1, 2, 3]); 119 | value.should.contain(1); 120 | value.should.contain(2); 121 | value.should.contain(3); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/support/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'matt walters' 2 | -------------------------------------------------------------------------------- /test/support/test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010, Chris Dickinson 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | # 6 | # Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | # Neither the name of the Node-Python binding nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | 12 | 13 | class Good(): 14 | def good(self): 15 | return 0; 16 | 17 | class Bad(): 18 | def bad(self): 19 | should cause parse error 20 | -------------------------------------------------------------------------------- /test/support/test2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010, Chris Dickinson 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | # 6 | # Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | # Neither the name of the Node-Python binding nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | def getPythonTypeName(value): 12 | return type(value).__name__ 13 | def getPythonTypeName2(value, index): 14 | item = value[index] 15 | return type(item).__name__ 16 | def getPythonValue(value): 17 | return value 18 | --------------------------------------------------------------------------------