diff --git a/src/klayout.pro b/src/klayout.pro index 888def688..2a3e1760a 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -39,6 +39,8 @@ equals(HAVE_PYTHON, "1") { SUBDIRS += pya LANG_DEPENDS += pya pya.depends += gsi db + SUBDIRS += pymod + pymod.depends += pya } else { SUBDIRS += pyastub pyastub.depends += gsi diff --git a/src/lay/lay/layGSIHelpProvider.cc b/src/lay/lay/layGSIHelpProvider.cc index 8fbfaedc4..5d30e8a4d 100644 --- a/src/lay/lay/layGSIHelpProvider.cc +++ b/src/lay/lay/layGSIHelpProvider.cc @@ -1190,10 +1190,7 @@ GSIHelpProvider::produce_class_doc (const std::string &cls) const const gsi::MethodBase::MethodSynonym &syn = i->second.first->begin_synonyms () [i->second.second]; DocumentationParser method_doc (i->second.first); - std::string pydoc; - if (pya::PythonInterpreter::instance ()) { - pydoc = pya::PythonInterpreter::instance ()->python_doc (i->second.first); - } + std::string pydoc = pya::PythonModule::python_doc (i->second.first); os << ""; if (i->first != prev_title) { diff --git a/src/pya/pya/pya.cc b/src/pya/pya/pya.cc index 755d1a2bc..cdd5e7382 100644 --- a/src/pya/pya/pya.cc +++ b/src/pya/pya/pya.cc @@ -60,7 +60,7 @@ class PYAObjectBase; #define PYA_TRY \ { \ - try { + try { #define PYA_CATCH(where) \ } catch (tl::ExitException &ex) { \ @@ -75,8 +75,8 @@ class PYAObjectBase; std::string msg = tl::to_string (QObject::tr ("Unspecific exception in ")) + (where); \ PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \ } \ - } - + } + /** * @brief The python interpreter instance */ @@ -241,9 +241,9 @@ public: } /** - * @brief Adds a method to the table + * @brief Adds a method to the table */ - void add_method (const std::string &name, const gsi::MethodBase *mb) + void add_method (const std::string &name, const gsi::MethodBase *mb) { bool st = mb->is_static (); @@ -392,7 +392,7 @@ public: * This method must be called after the add_method calls have been used * to fill the table. It will remove duplicate entries and clean up memory. */ - void finish () + void finish () { for (std::vector::iterator m = m_table.begin (); m != m_table.end (); ++m) { m->finish (); @@ -429,12 +429,12 @@ private: /** * @brief Constructor * This constructor will create a method table for the given class and register - * this table under this class. + * this table under this class. * It is used by method_table_by_class only, hence it's private. */ MethodTable (const gsi::ClassBase *cls_decl) : m_method_offset (0), m_property_offset (0), mp_cls_decl (cls_decl) - { + { if (cls_decl->base ()) { const MethodTable *base_mt = method_table_by_class (cls_decl->base ()); tl_assert (base_mt); @@ -452,8 +452,8 @@ class PythonStackTraceProvider { public: PythonStackTraceProvider (PyFrameObject *frame, const std::string &scope) - : m_scope (scope) - { + : m_scope (scope) + { while (frame != NULL) { int line = frame->f_lineno; @@ -468,7 +468,7 @@ public: } } - virtual std::vector stack_trace () const + virtual std::vector stack_trace () const { return m_stack_trace; } @@ -500,7 +500,7 @@ private: /** * @brief Gets the method name from a method id */ -std::string +std::string method_name_from_id (int mid, PyObject *self) { const gsi::ClassBase *cls_decl = 0; @@ -509,7 +509,7 @@ method_name_from_id (int mid, PyObject *self) PYAObjectBase *p = (PYAObjectBase *) self; cls_decl = p->cls_decl (); } else { - cls_decl = PythonInterpreter::instance ()->cls_for_type ((PyTypeObject *) self); + cls_decl = PythonModule::cls_for_type ((PyTypeObject *) self); } tl_assert (cls_decl != 0); @@ -533,7 +533,7 @@ method_name_from_id (int mid, PyObject *self) /** * @brief Gets the method name from a method id */ -std::string +std::string property_name_from_id (int mid, PyObject *self) { const gsi::ClassBase *cls_decl = 0; @@ -542,7 +542,7 @@ property_name_from_id (int mid, PyObject *self) PYAObjectBase *p = (PYAObjectBase *) self; cls_decl = p->cls_decl (); } else { - cls_decl = PythonInterpreter::instance ()->cls_for_type ((PyTypeObject *) self); + cls_decl = PythonModule::cls_for_type ((PyTypeObject *) self); } tl_assert (cls_decl != 0); @@ -563,7 +563,7 @@ property_name_from_id (int mid, PyObject *self) return cls_decl->name () + "." + mt->property_name (mid); } -static PyObject * +static PyObject * get_return_value (PYAObjectBase *self, gsi::SerialArgs &retlist, const gsi::MethodBase *meth, tl::Heap &heap) { PyObject *ret = NULL; @@ -603,7 +603,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) p = (PYAObjectBase *) self; cls_decl = p->cls_decl (); } else { - cls_decl = PythonInterpreter::instance ()->cls_for_type ((PyTypeObject *) self); + cls_decl = PythonModule::cls_for_type ((PyTypeObject *) self); } tl_assert (cls_decl != 0); @@ -632,7 +632,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if ((*m)->is_callback()) { // ignore callbacks - + } else if ((*m)->compatible_with_num_args (argc)) { ++candidates; @@ -647,7 +647,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if (! strict) { return 0; - } + } std::set nargs; for (MethodTableEntry::method_iterator m = mt->begin (mid); m != mt->end (mid); ++m) { @@ -756,7 +756,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) static PyObject * object_dup (PyObject *self, PyObject *args) { - const gsi::ClassBase *cls_decl_self = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (self)); + const gsi::ClassBase *cls_decl_self = PythonModule::cls_for_type (Py_TYPE (self)); tl_assert (cls_decl_self != 0); if (! PyArg_ParseTuple (args, "")) { @@ -781,7 +781,7 @@ object_dup (PyObject *self, PyObject *args) static PyObject * object_assign (PyObject *self, PyObject *args) { - const gsi::ClassBase *cls_decl_self = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (self)); + const gsi::ClassBase *cls_decl_self = PythonModule::cls_for_type (Py_TYPE (self)); tl_assert (cls_decl_self != 0); PyObject *src = NULL; @@ -789,12 +789,12 @@ object_assign (PyObject *self, PyObject *args) return NULL; } - const gsi::ClassBase *cls_decl_src = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (src)); + const gsi::ClassBase *cls_decl_src = PythonModule::cls_for_type (Py_TYPE (src)); tl_assert (cls_decl_src != 0); if (cls_decl_src != cls_decl_self) { throw tl::Exception (tl::to_string (QObject::tr ("Type is not identical on assign"))); - } + } if (! cls_decl_self->can_copy ()) { throw tl::Exception (tl::to_string (QObject::tr ("No assignment provided for class '%s'")), cls_decl_self->name ()); } @@ -858,7 +858,7 @@ object_default_le_impl (PyObject *self, PyObject *args) PythonRef lt_res (PyObject_Call (lt_method, args, NULL)); if (! lt_res) { return NULL; - } + } return c2python (python2c (eq_res.get ()) || python2c (lt_res.get ())); } @@ -881,7 +881,7 @@ object_default_gt_impl (PyObject *self, PyObject *args) PythonRef lt_res (PyObject_Call (lt_method, args, NULL)); if (! lt_res) { return NULL; - } + } return c2python (! (python2c (eq_res.get ()) || python2c (lt_res.get ()))); } @@ -1032,7 +1032,7 @@ method_adaptor (int mid, PyObject *self, PyObject *args) try { - int i = 0; + int i = 0; for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) { push_arg (*a, arglist, PyTuple_GetItem (args, i), heap); } @@ -1065,7 +1065,7 @@ method_adaptor (int mid, PyObject *self, PyObject *args) return ret; } -template +template PyObject *method_adaptor (PyObject *self, PyObject *args) { return method_adaptor (N, self, args); @@ -1256,7 +1256,7 @@ property_getter_adaptor (int mid, PyObject *self, PyObject *args) return ret; } -template +template PyObject *property_getter_adaptor (PyObject *self, PyObject *args) { return property_getter_adaptor (N, self, args); @@ -1418,7 +1418,7 @@ property_setter_adaptor (int mid, PyObject *self, PyObject *args) return ret; } -template +template PyObject *property_setter_adaptor (PyObject *self, PyObject *args) { return property_setter_adaptor (N, self, args); @@ -1582,7 +1582,7 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args) try { - int i = 0; + int i = 0; int argc = args == NULL ? 0 : int (PyTuple_Size (args)); for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) { push_arg (*a, arglist, PyTuple_GetItem (args, i), heap); @@ -1623,7 +1623,7 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args) return NULL; } -template +template PyObject *method_init_adaptor (PyObject *self, PyObject *args) { return method_init_adaptor (N, self, args); @@ -1789,7 +1789,7 @@ property_getter_impl (int mid, PyObject *self) p = (PYAObjectBase *) self; cls_decl = p->cls_decl (); } else { - cls_decl = PythonInterpreter::instance ()->cls_for_type ((PyTypeObject *) self); + cls_decl = PythonModule::cls_for_type ((PyTypeObject *) self); } const MethodTable *mt = MethodTable::method_table_by_class (cls_decl); @@ -1860,7 +1860,7 @@ property_getter_func (PyObject *self, void *closure) return ret; } -static PyObject * +static PyObject * property_setter_impl (int mid, PyObject *self, PyObject *value) { const gsi::ClassBase *cls_decl; @@ -1870,7 +1870,7 @@ property_setter_impl (int mid, PyObject *self, PyObject *value) p = (PYAObjectBase *) self; cls_decl = p->cls_decl (); } else { - cls_decl = PythonInterpreter::instance ()->cls_for_type ((PyTypeObject *) self); + cls_decl = PythonModule::cls_for_type ((PyTypeObject *) self); } if (p && p->const_ref ()) { @@ -2012,7 +2012,7 @@ property_setter_impl (int mid, PyObject *self, PyObject *value) } } -static int +static int property_setter_func (PyObject *self, PyObject *value, void *closure) { int res = -1; @@ -2052,7 +2052,7 @@ static int pya_object_init (PyObject * /*self*/, PyObject *args, PyObject *kwds) { // no particular initialization - static char *kwlist[] = {NULL}; + static char *kwlist[] = {NULL}; if (! PyArg_ParseTupleAndKeywords (args, kwds, "", kwlist)) { return -1; } else { @@ -2061,14 +2061,14 @@ pya_object_init (PyObject * /*self*/, PyObject *args, PyObject *kwds) } /** - * @brief Factory for a base class object + * @brief Factory for a base class object */ static PyObject * pya_object_new (PyTypeObject *type, PyObject * /*args*/, PyObject * /*kwds*/) { // create the object PYAObjectBase *self = (PYAObjectBase *) type->tp_alloc (type, 0); - new (self) PYAObjectBase (PythonInterpreter::instance ()->cls_for_type (type)); + new (self) PYAObjectBase (PythonModule::cls_for_type (type)); return (PyObject *) self; } @@ -2114,7 +2114,7 @@ static bool is_reserved_word (const std::string &name) } /** - * @brief Extracts the Python name from a generic name + * @brief Extracts the Python name from a generic name * * Returns an empty string if no Python name could be generated. */ @@ -2221,228 +2221,175 @@ static std::string extract_python_name (const std::string &name) } return name; - + } } + // -------------------------------------------------------------------------- -// The interpreter implementation +// The PythonModule implementation -static const char *pya_module_name = "pya"; +std::map PythonModule::m_python_doc; +std::map PythonModule::m_cls_map; +std::map PythonModule::m_rev_cls_map; -#if PY_MAJOR_VERSION < 3 - -static PyObject * -init_pya_module () +PythonModule::PythonModule () + : mp_mod_def (0) { + // .. nothing yet .. +} + +PythonModule::~PythonModule () +{ + PYAObjectBase::clear_callbacks_cache (); + + while (!m_methods_heap.empty ()) { + delete m_methods_heap.back (); + m_methods_heap.pop_back (); + } + + while (!m_getseters_heap.empty ()) { + delete m_getseters_heap.back (); + m_getseters_heap.pop_back (); + } + + m_string_heap.clear (); + + if (mp_mod_def) { + delete[] mp_mod_def; + mp_mod_def = 0; + } +} + +PyObject * +PythonModule::module () +{ + return mp_module.get (); +} + +void +PythonModule::init (const char *mod_name, const char *description) +{ + m_mod_name = mod_name; + m_mod_description = description; + static PyMethodDef module_methods[] = { - {NULL} // Sentinel + {NULL} // Sentinel }; - return Py_InitModule3 (pya_module_name, module_methods, "KLayout Python API."); -} + PyObject *module = 0; + +#if PY_MAJOR_VERSION < 3 + module = Py_InitModule3 (m_mod_name.c_str (), module_methods, m_mod_description.c_str ()); #else -static PyObject * -init_pya_module () + static struct PyModuleDef mod_def = { + PyModuleDef_HEAD_INIT, + m_mod_name.c_str (), + NULL, // module documentation + -1, // size of per-interpreter state of the module, + // if the module keeps state in global variables. + module_methods + }; + + tl_assert (mp_mod_def == 0); + + // prepare a persistent structure with the module definition + // and pass this one to PyModule_Create + mp_mod_def = new char[sizeof (PyModuleDef)]; + memcpy ((void *) mp_mod_def, (const void *) &mod_def, sizeof (PyModuleDef)); + module = PyModule_Create ((PyModuleDef *) mp_mod_def); + +#endif + + mp_module = PythonRef (module); +} + +void +PythonModule::init (const char *mod_name, PyObject *module) { - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - pya_module_name, // m_name - "KLayout Python API.", // m_doc - -1, // m_size - NULL, // m_methods - NULL, // m_reload - NULL, // m_traverse - NULL, // m_clear - NULL, // m_free - }; - return PyModule_Create (&moduledef); + m_mod_name = mod_name; + mp_module = PythonRef (module); } -#endif +PyMethodDef * +PythonModule::make_method_def () +{ + static PyMethodDef md = { }; + m_methods_heap.push_back (new PyMethodDef (md)); + return m_methods_heap.back (); +} -PythonInterpreter::PythonInterpreter () - : mp_current_console (0), mp_current_exec_handler (0), m_current_exec_level (0), - m_in_trace (false), m_block_exceptions (false), m_ignore_next_exception (false), - mp_current_frame (NULL), mp_py3_app_name (0) -{ - tl::SelfTimer timer (tl::verbosity () >= 21, "Initializing Python"); +PyGetSetDef * +PythonModule::make_getset_def () +{ + static PyGetSetDef gsd = { }; + m_getseters_heap.push_back (new PyGetSetDef (gsd)); + return m_getseters_heap.back (); +} - std::string app_path = tl::to_string (QCoreApplication::applicationFilePath ()); +char * +PythonModule::make_string (const std::string &s) +{ + m_string_heap.push_back (s); + return const_cast (m_string_heap.back ().c_str ()); +} -#if PY_MAJOR_VERSION >= 3 - - // if set, use $KLAYOUT_PYTHONPATH to initialize the path -# if defined(_WIN32) - - tl_assert (sizeof (wchar_t) == 2); - - const wchar_t *python_path = _wgetenv (L"KLAYOUT_PYTHONPATH"); - if (python_path) { - - Py_SetPath (python_path); +void +PythonModule::add_python_doc (const gsi::ClassBase & /*cls*/, const MethodTable *mt, int mid, const std::string &doc) +{ + for (MethodTableEntry::method_iterator m = mt->begin (mid); m != mt->end (mid); ++m) { + std::string &doc_string = m_python_doc [*m]; + doc_string += doc; + doc_string += "\n\n"; + } +} +std::string +PythonModule::python_doc (const gsi::MethodBase *method) +{ + std::map::const_iterator d = m_python_doc.find (method); + if (d != m_python_doc.end ()) { + return d->second; } else { - - // If present, read the paths from a file in INST_PATH/.python-paths.txt. - // The content of this file is evaluated as an expression and the result - // is placed inside the Python path. - - try { - - QString path; - - QDir inst_dir (QCoreApplication::applicationDirPath ()); - QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".python-paths.txt"))); - if (fi.exists ()) { - - tl::log << tl::to_string (QObject::tr ("Reading Python path from ")) << tl::to_string (fi.filePath ()); - - QFile paths_txt (fi.filePath ()); - paths_txt.open (QIODevice::ReadOnly); - - tl::Eval eval; - eval.set_global_var ("inst_path", tl::Variant (tl::to_string (inst_dir.absolutePath ()))); - tl::Expression ex; - eval.parse (ex, paths_txt.readAll ().constData ()); - tl::Variant v = ex.execute (); - - if (v.is_list ()) { - for (tl::Variant::iterator i = v.begin (); i != v.end (); ++i) { - if (! path.isEmpty ()) { - path += tl::to_qstring (";"); - } - path += tl::to_qstring (i->to_string ()); - } - } - - } - - // note: this is a hack, but linking with toWCharArray fails since wchar_t is treated - // as a built-in type in our build - Py_SetPath ((const wchar_t *) path.utf16 ()); - - } catch (tl::Exception &ex) { - tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")) << ": " << ex.msg (); - } catch (...) { - tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")); - } - + return std::string (); } +} -# else +void +PythonModule::make_classes () +{ + PyObject *module = mp_module.get (); - const char *python_path = getenv ("KLAYOUT_PYTHONPATH"); - if (python_path) { + // Create a (built-in) base class for all objects exposed by this module - QString path = QString::fromLocal8Bit (python_path); + m_base_class_name = m_mod_name + ".__Base"; - if (sizeof (wchar_t) == 4) { - Py_SetPath ((const wchar_t *) path.toUcs4 ().constData ()); - } else if (sizeof (wchar_t) == 2) { - Py_SetPath ((const wchar_t *) path.utf16 ()); - } - - } - -# endif - -#endif + PyTypeObject *base_class = (PyTypeObject *) PyType_Type.tp_alloc (&PyType_Type, sizeof (PyTypeObject)); + tl_assert (base_class != NULL); + mp_base_class = PythonRef ((PyObject *) base_class); + base_class->tp_base = &PyBaseObject_Type; + base_class->tp_name = m_base_class_name.c_str (); + base_class->tp_basicsize = sizeof (PYAObjectBase); + base_class->tp_init = &pya_object_init; + base_class->tp_new = &pya_object_new; + base_class->tp_dealloc = (destructor) &pya_object_deallocate; + base_class->tp_setattro = PyObject_GenericSetAttr; + base_class->tp_getattro = PyObject_GenericGetAttr; #if PY_MAJOR_VERSION < 3 - - Py_SetProgramName (make_string (app_path)); - - Py_InitializeEx (0 /*don't set signals*/); - - // Set dummy argv[] - // TODO: more? - char *argv[1] = { make_string (app_path) }; -#if PY_MINOR_VERSION >= 7 - PySys_SetArgvEx (1, argv, 0); + base_class->tp_flags = Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES; #else - PySys_SetArgv (1, argv); + base_class->tp_flags = Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; #endif - PyObject *module = init_pya_module (); - if (module == NULL) { + if (PyType_Ready (base_class) < 0) { check_error (); return; } - PyImport_ImportModule (pya_module_name); + PyModule_AddObject (module, "__Base", (PyObject *) base_class); -#else - - // Python 3 requires a unicode string for the application name - PyObject *an = c2python (app_path); - tl_assert (an != NULL); - mp_py3_app_name = PyUnicode_AsWideCharString (an, NULL); - tl_assert (mp_py3_app_name != NULL); - Py_DECREF (an); - Py_SetProgramName (mp_py3_app_name); - - PyImport_AppendInittab (pya_module_name, &init_pya_module); - Py_InitializeEx (0 /*don't set signals*/); - - // Set dummy argv[] - // TODO: more? - wchar_t *argv[1] = { mp_py3_app_name }; - PySys_SetArgvEx (1, argv, 0); - - // Import the module - PyObject *module = PyImport_ImportModule (pya_module_name); - if (module == NULL) { - check_error (); - return; - } - -#endif - - m_object_heap.push_back (PythonRef (module)); - - - // Create a (built-in) base class for all objects exposed by pya - - static PyTypeObject base_class = { - PyVarObject_HEAD_INIT (&PyType_Type, 0) - "pya.__Base", // tp_name - sizeof (PYAObjectBase) // tp_size - }; - - m_object_heap.push_back (PythonRef ((PyObject *) &base_class)); - base_class.tp_base = &PyBaseObject_Type; - base_class.tp_basicsize = sizeof (PYAObjectBase); - base_class.tp_init = &pya_object_init; - base_class.tp_new = &pya_object_new; - base_class.tp_dealloc = (destructor) &pya_object_deallocate; -#if PY_MAJOR_VERSION < 3 - base_class.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES; -#else - base_class.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; -#endif - base_class.tp_setattro = PyObject_GenericSetAttr; - base_class.tp_getattro = PyObject_GenericGetAttr; - - if (PyType_Ready (&base_class) < 0) { - check_error (); - return; - } - - PyType_Ready (&base_class); - Py_INCREF (&base_class); - - PyModule_AddObject (module, "__Base", (PyObject *) &base_class); - - - // Build two objects that provide a way to redirect stdout, stderr - // and instatiate them two times for stdout and stderr. - PYAChannelObject::make_class (module); - m_stdout_channel = PythonRef (PYAChannelObject::create (gsi::Console::OS_stdout)); - m_stdout = PythonPtr (m_stdout_channel.get ()); - m_stderr_channel = PythonRef (PYAChannelObject::create (gsi::Console::OS_stderr)); - m_stderr = PythonPtr (m_stderr_channel.get ()); // Build a class for descriptors for static attributes PYAStaticAttributeDescriptorObject::make_class (module); @@ -2493,17 +2440,17 @@ PythonInterpreter::PythonInterpreter () // Create the class as a heap object, since that way we can dynamically extend the objects PythonRef bases (PyTuple_New (1)); - PyTypeObject *base = &base_class; + PyObject *base = mp_base_class.get (); if (c->base () != 0) { std::map ::const_iterator cb = m_rev_cls_map.find (c->base ()); tl_assert (cb != m_rev_cls_map.end ()); - base = cb->second; + base = (PyObject *) cb->second; } Py_INCREF (base); - PyTuple_SetItem (bases.get (), 0, (PyObject *) base); + PyTuple_SetItem (bases.get (), 0, base); PythonRef dict (PyDict_New ()); - PyDict_SetItemString (dict.get (), "__module__", PythonRef (c2python (pya_module_name)).get ()); + PyDict_SetItemString (dict.get (), "__module__", PythonRef (c2python (m_mod_name)).get ()); PyDict_SetItemString (dict.get (), "__doc__", PythonRef (c2python (c->doc ())).get ()); PythonRef args (PyTuple_New (3)); @@ -2511,7 +2458,7 @@ PythonInterpreter::PythonInterpreter () PyTuple_SetItem (args.get (), 1, bases.release ()); PyTuple_SetItem (args.get (), 2, dict.release ()); - PyTypeObject *type = (PyTypeObject *) PyObject_Call ((PyObject *) &PyType_Type, args.get (), NULL); + PyTypeObject *type = (PyTypeObject *) PyObject_Call ((PyObject *) &PyType_Type, args.get (), NULL); tl_assert (type != NULL); PyModule_AddObject (module, c->name ().c_str (), (PyObject *) type); @@ -2614,7 +2561,7 @@ PythonInterpreter::PythonInterpreter () std::string doc; // add getter and setter documentation, create specific Python documentation - + for (MethodTableEntry::method_iterator m = begin_getters; m != end_getters; ++m) { if (! doc.empty ()) { doc += "\n\n"; @@ -2638,8 +2585,8 @@ PythonInterpreter::PythonInterpreter () // non-static attribute getters/setters PyGetSetDef *getset = make_getset_def (); getset->name = make_string (name); - getset->get = begin_getters != end_getters ? &property_getter_func : NULL; - getset->set = begin_setters != end_setters ? &property_setter_func : NULL; + getset->get = begin_getters != end_getters ? &property_getter_func : NULL; + getset->set = begin_setters != end_setters ? &property_setter_func : NULL; getset->doc = make_string (doc); getset->closure = make_closure (getter_mid, setter_mid); @@ -2758,7 +2705,7 @@ PythonInterpreter::PythonInterpreter () } else if (name == "size" && m_first->compatible_with_num_args (0)) { - // The size method is also routed via the sequence methods protocol if there + // The size method is also routed via the sequence methods protocol if there // is a [] function add_python_doc (*c, mt, mid, tl::to_string (QObject::tr ("This method is also available as 'len(object)'"))); alt_names.push_back ("__len__"); @@ -2796,7 +2743,7 @@ PythonInterpreter::PythonInterpreter () set_type_attr (type, name, attr); } else if (isupper (name [0]) || m_first->is_const ()) { - + if ((mt->end (mid) - mt->begin (mid)) == 1 && m_first->begin_arguments () == m_first->end_arguments ()) { // static methods without arguments which start with a capital letter are treated as constants @@ -2849,13 +2796,13 @@ PythonInterpreter::PythonInterpreter () // Unlike Ruby, Python does not automatically implement != from == for example. // We assume that "==" and "<" are the minimum requirements for full comparison // and "==" is the minimum requirement for equality. Hence: - // * If "==" is given, but no "!=", synthesize + // * If "==" is given, but no "!=", synthesize // "a != b" by "!a == b" // * If "==" and "<" are given, synthesize if required // "a <= b" by "a < b || a == b" // "a > b" by "!(a < b || a == b)" (could be b < a, but this avoids having to switch arguments) // "a >= b" by "!a < b" - + bool has_eq = mt->find_method (false, "==").first; bool has_ne = mt->find_method (false, "!=").first; bool has_lt = mt->find_method (false, "<").first; @@ -2951,25 +2898,222 @@ PythonInterpreter::PythonInterpreter () } } +} + +const gsi::ClassBase *PythonModule::cls_for_type (PyTypeObject *type) +{ + while (type) { + std::map ::const_iterator t = m_cls_map.find (type); + if (t != m_cls_map.end ()) { + return t->second; + } + // not found - try base class + type = type->tp_base; + } + return 0; +} + +PyTypeObject *PythonModule::type_for_cls (const gsi::ClassBase *cls) +{ + std::map ::const_iterator s = m_rev_cls_map.find (cls); + if (s == m_rev_cls_map.end ()) { + return NULL; + } else { + return s->second; + } +} + +// -------------------------------------------------------------------------- +// The interpreter implementation + +static const char *pya_module_name = "pya"; + +#if PY_MAJOR_VERSION < 3 + +static PyObject * +init_pya_module () +{ + static PyMethodDef module_methods[] = { + {NULL} // Sentinel + }; + return Py_InitModule3 (pya_module_name, module_methods, "KLayout Python API."); +} + +#else + +static PyObject * +init_pya_module () +{ + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + pya_module_name, // m_name + "KLayout Python API.", // m_doc + -1, // m_size + NULL, // m_methods + NULL, // m_reload + NULL, // m_traverse + NULL, // m_clear + NULL, // m_free + }; + return PyModule_Create (&moduledef); +} + +#endif + +PythonInterpreter::PythonInterpreter () + : mp_current_console (0), mp_current_exec_handler (0), m_current_exec_level (0), + m_in_trace (false), m_block_exceptions (false), m_ignore_next_exception (false), + mp_current_frame (NULL), mp_py3_app_name (0) +{ + tl::SelfTimer timer (tl::verbosity () >= 21, "Initializing Python"); + + std::string app_path = tl::to_string (QCoreApplication::applicationFilePath ()); + +#if PY_MAJOR_VERSION >= 3 + + // if set, use $KLAYOUT_PYTHONPATH to initialize the path +# if defined(_WIN32) + + tl_assert (sizeof (wchar_t) == 2); + + const wchar_t *python_path = _wgetenv (L"KLAYOUT_PYTHONPATH"); + if (python_path) { + + Py_SetPath (python_path); + + } else { + + // If present, read the paths from a file in INST_PATH/.python-paths.txt. + // The content of this file is evaluated as an expression and the result + // is placed inside the Python path. + + try { + + QString path; + + QDir inst_dir (QCoreApplication::applicationDirPath ()); + QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".python-paths.txt"))); + if (fi.exists ()) { + + tl::log << tl::to_string (QObject::tr ("Reading Python path from ")) << tl::to_string (fi.filePath ()); + + QFile paths_txt (fi.filePath ()); + paths_txt.open (QIODevice::ReadOnly); + + tl::Eval eval; + eval.set_global_var ("inst_path", tl::Variant (tl::to_string (inst_dir.absolutePath ()))); + tl::Expression ex; + eval.parse (ex, paths_txt.readAll ().constData ()); + tl::Variant v = ex.execute (); + + if (v.is_list ()) { + for (tl::Variant::iterator i = v.begin (); i != v.end (); ++i) { + if (! path.isEmpty ()) { + path += tl::to_qstring (";"); + } + path += tl::to_qstring (i->to_string ()); + } + } + + } + + // note: this is a hack, but linking with toWCharArray fails since wchar_t is treated + // as a built-in type in our build + Py_SetPath ((const wchar_t *) path.utf16 ()); + + } catch (tl::Exception &ex) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")) << ": " << ex.msg (); + } catch (...) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")); + } + + } + +# else + + const char *python_path = getenv ("KLAYOUT_PYTHONPATH"); + if (python_path) { + + QString path = QString::fromLocal8Bit (python_path); + + if (sizeof (wchar_t) == 4) { + Py_SetPath ((const wchar_t *) path.toUcs4 ().constData ()); + } else if (sizeof (wchar_t) == 2) { + Py_SetPath ((const wchar_t *) path.utf16 ()); + } + + } + +# endif + +#endif + +#if PY_MAJOR_VERSION < 3 + + Py_SetProgramName (make_string (app_path)); + + Py_InitializeEx (0 /*don't set signals*/); + + // Set dummy argv[] + // TODO: more? + char *argv[1] = { make_string (app_path) }; +#if PY_MINOR_VERSION >= 7 + PySys_SetArgvEx (1, argv, 0); +#else + PySys_SetArgv (1, argv); +#endif + + PyObject *module = init_pya_module (); + if (module == NULL) { + check_error (); + return; + } + + PyImport_ImportModule (pya_module_name); + +#else + + // Python 3 requires a unicode string for the application name + PyObject *an = c2python (app_path); + tl_assert (an != NULL); + mp_py3_app_name = PyUnicode_AsWideCharString (an, NULL); + tl_assert (mp_py3_app_name != NULL); + Py_DECREF (an); + Py_SetProgramName (mp_py3_app_name); + + PyImport_AppendInittab (pya_module_name, &init_pya_module); + Py_InitializeEx (0 /*don't set signals*/); + + // Set dummy argv[] + // TODO: more? + wchar_t *argv[1] = { mp_py3_app_name }; + PySys_SetArgvEx (1, argv, 0); + + // Import the module + PyObject *module = PyImport_ImportModule (pya_module_name); + if (module == NULL) { + check_error (); + return; + } + +#endif + + // Build two objects that provide a way to redirect stdout, stderr + // and instatiate them two times for stdout and stderr. + PYAChannelObject::make_class (module); + m_stdout_channel = PythonRef (PYAChannelObject::create (gsi::Console::OS_stdout)); + m_stdout = PythonPtr (m_stdout_channel.get ()); + m_stderr_channel = PythonRef (PYAChannelObject::create (gsi::Console::OS_stderr)); + m_stderr = PythonPtr (m_stderr_channel.get ()); + + m_pya_module.init (pya_module_name, module); + m_pya_module.make_classes (); sp_interpreter = this; } -PythonInterpreter::~PythonInterpreter () +PythonInterpreter::~PythonInterpreter () { - m_object_heap.clear (); - PYAObjectBase::clear_callbacks_cache (); - - while (!m_methods_heap.empty ()) { - delete m_methods_heap.back (); - m_methods_heap.pop_back (); - } - - while (!m_getseters_heap.empty ()) { - delete m_getseters_heap.back (); - m_getseters_heap.pop_back (); - } - m_stdout_channel = PythonRef (); m_stderr_channel = PythonRef (); m_stdout = PythonPtr (); @@ -2977,8 +3121,6 @@ PythonInterpreter::~PythonInterpreter () Py_Finalize (); - m_string_heap.clear (); - if (mp_py3_app_name) { PyMem_Free (mp_py3_app_name); mp_py3_app_name = 0; @@ -2987,40 +3129,7 @@ PythonInterpreter::~PythonInterpreter () sp_interpreter = 0; } -PyMethodDef * -PythonInterpreter::make_method_def () -{ - static PyMethodDef md = { }; - m_methods_heap.push_back (new PyMethodDef (md)); - return m_methods_heap.back (); -} - -PyGetSetDef * -PythonInterpreter::make_getset_def () -{ - static PyGetSetDef gsd = { }; - m_getseters_heap.push_back (new PyGetSetDef (gsd)); - return m_getseters_heap.back (); -} - -char * -PythonInterpreter::make_string (const std::string &s) -{ - m_string_heap.push_back (s); - return const_cast (m_string_heap.back ().c_str ()); -} - -void -PythonInterpreter::add_python_doc (const gsi::ClassBase & /*cls*/, const MethodTable *mt, int mid, const std::string &doc) -{ - for (MethodTableEntry::method_iterator m = mt->begin (mid); m != mt->end (mid); ++m) { - std::string &doc_string = m_python_doc [*m]; - doc_string += doc; - doc_string += "\n\n"; - } -} - -void +void PythonInterpreter::add_path (const std::string &p) { PyObject *path = PySys_GetObject ((char *) "path"); @@ -3052,19 +3161,19 @@ PythonInterpreter::require (const std::string & /*filename*/) throw tl::Exception (tl::to_string (QObject::tr ("'require' not implemented for Python interpreter"))); } -void +void PythonInterpreter::set_debugger_scope (const std::string &filename) { m_debugger_scope = filename; } -void +void PythonInterpreter::remove_debugger_scope () { m_debugger_scope.clear (); } -void +void PythonInterpreter::ignore_next_exception () { if (mp_current_exec_handler) { @@ -3082,7 +3191,7 @@ PythonInterpreter::load_file (const std::string &filename) /** * @brief Gets the global and local variable lists for a given context index */ -void +void PythonInterpreter::get_context (int context, PythonRef &globals, PythonRef &locals, const char *file) { globals = PythonRef (); @@ -3113,7 +3222,7 @@ PythonInterpreter::get_context (int context, PythonRef &globals, PythonRef &loca globals = dict; locals = dict; - + if (file) { PythonRef fn (c2python (file)); @@ -3154,7 +3263,7 @@ PythonInterpreter::eval_string (const char *expr, const char *file, int /*line*/ /** * @brief Evaluates the given expression or executes the given statement * The expression is given int "string". If "eval_expr" is true, the string is evaluated as expression - * and the result is returned in the variant. If "eval_expr" is false, the string is evaluated, the + * and the result is returned in the variant. If "eval_expr" is false, the string is evaluated, the * result is printed to the currently active console and a nil variant is returned. */ tl::Variant @@ -3235,7 +3344,7 @@ PythonInterpreter::available () const return true; } -void +void PythonInterpreter::initialize () { // .. no implementation required .. @@ -3247,19 +3356,19 @@ PythonInterpreter::prepare_trace (PyObject *fn_object) std::map::const_iterator f = m_file_id_map.find (fn_object); if (f == m_file_id_map.end ()) { f = m_file_id_map.insert (std::make_pair (fn_object, mp_current_exec_handler->id_for_path (this, normalize_path (python2c (fn_object))))).first; - } + } return f->second; } // TODO: make the Python object the interpreter and don't use singleton instances (multi-threading support) -static +static int pya_trace_func (PyObject * /*obj*/, PyFrameObject *frame, int event, PyObject *arg) { return PythonInterpreter::instance ()->trace_func (frame, event, arg); } -int +int PythonInterpreter::trace_func (PyFrameObject *frame, int event, PyObject *arg) { if (! mp_current_exec_handler || m_in_trace) { @@ -3333,8 +3442,8 @@ PythonInterpreter::trace_func (PyFrameObject *frame, int event, PyObject *arg) } // TODO: really needed? - // Ruby tends to call this callback twice - once from rb_f_raise and then - // from rb_exc_raise. We use the m_block_exceptions flag to suppress the + // Ruby tends to call this callback twice - once from rb_f_raise and then + // from rb_exc_raise. We use the m_block_exceptions flag to suppress the // second one m_block_exceptions = true; @@ -3352,7 +3461,7 @@ PythonInterpreter::trace_func (PyFrameObject *frame, int event, PyObject *arg) return -1; } -void +void PythonInterpreter::push_exec_handler (gsi::ExecutionHandler *exec_handler) { if (mp_current_exec_handler) { @@ -3364,7 +3473,7 @@ PythonInterpreter::push_exec_handler (gsi::ExecutionHandler *exec_handler) mp_current_exec_handler = exec_handler; m_file_id_map.clear (); - // if we happen to push the exec handler inside the execution, + // if we happen to push the exec handler inside the execution, // signal start of execution if (m_current_exec_level > 0) { mp_current_exec_handler->start_exec (this); @@ -3376,7 +3485,7 @@ PythonInterpreter::remove_exec_handler (gsi::ExecutionHandler *exec_handler) { if (mp_current_exec_handler == exec_handler) { - // if we happen to remove the exec handler inside the execution, + // if we happen to remove the exec handler inside the execution, // signal end of execution if (m_current_exec_level > 0) { mp_current_exec_handler->end_exec (this); @@ -3402,10 +3511,10 @@ PythonInterpreter::remove_exec_handler (gsi::ExecutionHandler *exec_handler) } } -void +void PythonInterpreter::push_console (gsi::Console *console) { - if (! mp_current_console) { + if (! mp_current_console) { PythonPtr current_stdout (PySys_GetObject ((char *) "stdout")); std::swap (current_stdout, m_stdout); @@ -3426,7 +3535,7 @@ PythonInterpreter::push_console (gsi::Console *console) mp_current_console = console; } -void +void PythonInterpreter::remove_console (gsi::Console *console) { if (mp_current_console == console) { @@ -3480,53 +3589,19 @@ gsi::Console *PythonInterpreter::current_console () const return mp_current_console; } -const gsi::ClassBase *PythonInterpreter::cls_for_type (PyTypeObject *type) const -{ - while (type) { - std::map ::const_iterator t = m_cls_map.find (type); - if (t != m_cls_map.end ()) { - return t->second; - } - // not found - try base class - type = type->tp_base; - } - return 0; -} - -PyTypeObject *PythonInterpreter::type_for_cls (const gsi::ClassBase *cls) const -{ - std::map ::const_iterator s = m_rev_cls_map.find (cls); - if (s == m_rev_cls_map.end ()) { - return NULL; - } else { - return s->second; - } -} - void PythonInterpreter::begin_execution () { - m_file_id_map.clear (); - m_block_exceptions = false; - if (m_current_exec_level++ == 0 && mp_current_exec_handler) { - mp_current_exec_handler->start_exec (this); + m_file_id_map.clear (); + m_block_exceptions = false; + if (m_current_exec_level++ == 0 && mp_current_exec_handler) { + mp_current_exec_handler->start_exec (this); } } void PythonInterpreter::end_execution () { - if (m_current_exec_level > 0 && --m_current_exec_level == 0 && mp_current_exec_handler) { - mp_current_exec_handler->end_exec (this); - } -} - -std::string -PythonInterpreter::python_doc (const gsi::MethodBase *method) const -{ - std::map::const_iterator d = m_python_doc.find (method); - if (d != m_python_doc.end ()) { - return d->second; - } else { - return std::string (); + if (m_current_exec_level > 0 && --m_current_exec_level == 0 && mp_current_exec_handler) { + mp_current_exec_handler->end_exec (this); } } diff --git a/src/pya/pya/pya.h b/src/pya/pya/pya.h index 592b3337b..27eedd30a 100644 --- a/src/pya/pya/pya.h +++ b/src/pya/pya/pya.h @@ -34,6 +34,7 @@ #include #include #include +#include struct _typeobject; typedef _typeobject PyTypeObject; @@ -89,6 +90,84 @@ public: class MethodTable; +/** + * @brief A representative for a Python module + * Instantiate this object to represent a python module. + * If used externally (for a library), call init with the module name. + * Then call make_classes to create the individual classes. + */ +class PYA_PUBLIC PythonModule +{ +public: + /** + * @brief Constructor + */ + PythonModule (); + + /** + * @brief Destructor + */ + ~PythonModule (); + + /** + * @brief Initializes the module + * This entry point is for external use where the module has not been created yet + */ + void init (const char *mod_name, const char *description); + + /** + * @brief Initializes the module + * This entry point is for internal use where the module is created by the interpreter + */ + void init (const char *mod_name, PyObject *module); + + /** + * @brief Creates the classes after init has been called + */ + void make_classes (); + + /** + * @brief Gets the GSI class for a Python class + */ + static const gsi::ClassBase *cls_for_type (PyTypeObject *type); + + /** + * @brief The reverse: gets a Python class for a GSI class or NULL if there is no binding + */ + static PyTypeObject *type_for_cls (const gsi::ClassBase *cls); + + /** + * @brief Returns additional Python-specific documentation for the given method + * If no specific documentation exists, an empty string is returned. + */ + static std::string python_doc (const gsi::MethodBase *method); + + /** + * @brief Gets the PyObject for the module + */ + PyObject *module (); + +private: + void add_python_doc (const gsi::ClassBase &cls, const MethodTable *mt, int mid, const std::string &doc); + PyMethodDef *make_method_def (); + PyGetSetDef *make_getset_def (); + char *make_string (const std::string &s); + + std::list m_string_heap; + std::vector m_methods_heap; + std::vector m_getseters_heap; + + std::string m_mod_name, m_mod_description; + std::string m_base_class_name; + PythonRef mp_module; + PythonRef mp_base_class; + char *mp_mod_def; + + static std::map m_python_doc; + static std::map m_cls_map; + static std::map m_rev_cls_map; +}; + /** * @brief The python interpreter wrapper class */ @@ -214,16 +293,6 @@ public: */ std::string version () const; - /** - * @brief Gets the GSI class for a Python class - */ - const gsi::ClassBase *cls_for_type (PyTypeObject *type) const; - - /** - * @brief The reverse: gets a Python class for a GSI class or NULL if there is no binding - */ - PyTypeObject *type_for_cls (const gsi::ClassBase *cls) const; - /** * @brief Returns the current console */ @@ -246,12 +315,6 @@ public: */ int trace_func (PyFrameObject *frame, int event, PyObject *arg); - /** - * @brief Returns additional Python-specific documentation for the given method - * If no specific documentation exists, an empty string is returned. - */ - std::string python_doc (const gsi::MethodBase *method) const; - /** * @brief Returns the singleton reference */ @@ -266,22 +329,11 @@ private: size_t prepare_trace (PyObject *); tl::Variant eval_int (const char *string, const char *filename, int line, bool eval_expr, int context); void get_context (int context, PythonRef &globals, PythonRef &locals, const char *file); - void add_python_doc (const gsi::ClassBase &cls, const MethodTable *mt, int mid, const std::string &doc); - PyMethodDef *make_method_def (); - PyGetSetDef *make_getset_def (); - char *make_string (const std::string &s); - std::list m_object_heap; - std::list m_string_heap; - std::map m_python_doc; - std::set m_package_paths; - std::vector m_methods_heap; - std::vector m_getseters_heap; PythonRef m_stdout_channel, m_stderr_channel; PythonPtr m_stdout, m_stderr; + std::set m_package_paths; - std::map m_cls_map; - std::map m_rev_cls_map; gsi::Console *mp_current_console; std::vector m_consoles; gsi::ExecutionHandler *mp_current_exec_handler; @@ -296,6 +348,7 @@ private: PyFrameObject *mp_current_frame; std::map m_file_id_map; wchar_t *mp_py3_app_name; + pya::PythonModule m_pya_module; }; } diff --git a/src/pya/pya/pyaCommon.h b/src/pya/pya/pyaCommon.h index 90a30727c..e499da69b 100644 --- a/src/pya/pya/pyaCommon.h +++ b/src/pya/pya/pyaCommon.h @@ -20,32 +20,19 @@ */ +#include "tlDefs.h" #if !defined(HDR_pyaCommon_h) # define HDR_pyaCommon_h -# if defined _WIN32 || defined __CYGWIN__ - -# ifdef MAKE_PYA_LIBRARY -# define PYA_PUBLIC __declspec(dllexport) -# else -# define PYA_PUBLIC __declspec(dllimport) -# endif -# define PYA_LOCAL -# define PYA_PUBLIC_TEMPLATE - +# ifdef MAKE_PYA_LIBRARY +# define PYA_PUBLIC DEF_INSIDE_PUBLIC +# define PYA_PUBLIC_TEMPLATE DEF_INSIDE_PUBLIC_TEMPLATE +# define PYA_LOCAL DEF_INSIDE_LOCAL # else - -# if __GNUC__ >= 4 || defined(__clang__) -# define PYA_PUBLIC __attribute__ ((visibility ("default"))) -# define PYA_PUBLIC_TEMPLATE __attribute__ ((visibility ("default"))) -# define PYA_LOCAL __attribute__ ((visibility ("hidden"))) -# else -# define PYA_PUBLIC -# define PYA_PUBLIC_TEMPLATE -# define PYA_LOCAL -# endif - +# define PYA_PUBLIC DEF_OUTSIDE_PUBLIC +# define PYA_PUBLIC_TEMPLATE DEF_OUTSIDE_PUBLIC_TEMPLATE +# define PYA_LOCAL DEF_OUTSIDE_LOCAL # endif #endif diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index e6bf1c3c0..d8ba93cb9 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -309,7 +309,7 @@ tl::Variant python2c (PyObject *rval, tl::Heap *heap) } else { - const gsi::ClassBase *cls = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (rval)); + const gsi::ClassBase *cls = PythonModule::cls_for_type (Py_TYPE (rval)); if (cls) { PYAObjectBase *p = (PYAObjectBase *) rval; @@ -438,7 +438,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo // can't guarantee the lifetime of the container will exceed that // of the exposed property. Hence copying is safer. - PyTypeObject *type = PythonInterpreter::instance ()->type_for_cls (clsact); + PyTypeObject *type = PythonModule::type_for_cls (clsact); tl_assert (type != NULL); // create a instance and copy the value @@ -459,7 +459,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo } else { - PyTypeObject *type = PythonInterpreter::instance ()->type_for_cls (clsact); + PyTypeObject *type = PythonModule::type_for_cls (clsact); tl_assert (type != NULL); // create a instance and copy the value diff --git a/src/pya/pya/pyaMarshal.cc b/src/pya/pya/pyaMarshal.cc index d3dac6c6c..8356e7a35 100644 --- a/src/pya/pya/pyaMarshal.cc +++ b/src/pya/pya/pyaMarshal.cc @@ -36,9 +36,9 @@ namespace pya // Serialization adaptors for strings, variants, vectors and maps /** - * @brief An adaptor for a string from ruby objects + * @brief An adaptor for a string from ruby objects */ -class PythonBasedStringAdaptor +class PythonBasedStringAdaptor : public gsi::StringAdaptor { public: @@ -69,9 +69,9 @@ private: }; /** - * @brief An adaptor for a variant from ruby objects + * @brief An adaptor for a variant from ruby objects */ -class PythonBasedVariantAdaptor +class PythonBasedVariantAdaptor : public gsi::VariantAdaptor { public: @@ -106,7 +106,7 @@ private: /** * @brief An adaptor for a vector from Python objects */ -class PythonBasedVectorAdaptor +class PythonBasedVectorAdaptor : public gsi::VectorAdaptor { public: @@ -132,7 +132,7 @@ class PythonBasedMapAdaptorIterator public: PythonBasedMapAdaptorIterator (const PythonPtr &hash, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k); - virtual void get (gsi::SerialArgs &w, tl::Heap &heap) const; + virtual void get (gsi::SerialArgs &w, tl::Heap &heap) const; virtual bool at_end () const; virtual void inc (); @@ -147,7 +147,7 @@ private: /** * @brief An adaptor for a map from Python objects */ -class PythonBasedMapAdaptor +class PythonBasedMapAdaptor : public gsi::MapAdaptor { public: @@ -171,8 +171,8 @@ template struct get_boxed_value_func { void operator() (void **ret, PyObject *arg, tl::Heap *heap) - { - const gsi::ClassBase *cls_decl = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (arg)); + { + const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg)); if (! cls_decl) { R *v = new R (python2c (arg, heap)); @@ -187,7 +187,7 @@ struct get_boxed_value_func throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Passing an object to pointer or reference requires a boxed type (pya.%s)")), bt->name ())); } - PYAObjectBase *p = (PYAObjectBase *) arg; + PYAObjectBase *p = (PYAObjectBase *) arg; gsi::Value *bo = reinterpret_cast (p->obj ()); if (bo) { *ret = bo->value ().template morph ().native_ptr (); @@ -254,8 +254,8 @@ struct writer } }; -/** - * @brief Serialization for strings +/** + * @brief Serialization for strings */ template <> struct writer @@ -362,7 +362,7 @@ struct writer /** * @brief A serialization wrapper (write mode) - * Specialisation for objects + * Specialisation for objects */ template <> struct writer @@ -382,14 +382,14 @@ struct writer if (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ()) { - const gsi::ClassBase *cls_decl = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (arg)); + const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg)); if (! cls_decl) { throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name)); } if (cls_decl->is_derived_from (atype.cls ())) { - PYAObjectBase *p = (PYAObjectBase *) (arg); + PYAObjectBase *p = (PYAObjectBase *) (arg); if (cls_decl->adapted_type_info ()) { // resolved adapted type @@ -400,7 +400,7 @@ struct writer } else if (cls_decl->can_convert_to (atype.cls ())) { - PYAObjectBase *p = (PYAObjectBase *) (arg); + PYAObjectBase *p = (PYAObjectBase *) (arg); // We can convert objects for cref and cptr, but ownership over these objects is not transferred. // Hence we have to keep them on the heap. @@ -414,14 +414,14 @@ struct writer } else { - const gsi::ClassBase *cls_decl = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (arg)); + const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg)); if (! cls_decl) { throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name)); } if (cls_decl->is_derived_from (atype.cls ())) { - PYAObjectBase *p = (PYAObjectBase *) (arg); + PYAObjectBase *p = (PYAObjectBase *) (arg); if (cls_decl->adapted_type_info ()) { // resolved adapted type @@ -432,7 +432,7 @@ struct writer } else if (cls_decl->can_convert_to (atype.cls ())) { - PYAObjectBase *p = (PYAObjectBase *) (arg); + PYAObjectBase *p = (PYAObjectBase *) (arg); aa->write (atype.cls ()->create_obj_from (cls_decl, p->obj ())); } else { @@ -446,7 +446,7 @@ struct writer /** * @brief A serialization wrapper (write mode) - * Specialisation for void + * Specialisation for void */ template <> struct writer @@ -464,11 +464,11 @@ push_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PyObject *arg, tl } /** - * @brief Deseralisation wrapper + * @brief Deseralisation wrapper * * The default implementation is for POD types, strings and variants */ -template +template struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &arg, tl::Heap *heap) @@ -503,7 +503,7 @@ struct reader * Without that would would have to handle void *&, void * const &, ... * TODO: right now these types are not supported. */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &arg, tl::Heap *heap) @@ -519,7 +519,7 @@ struct reader /** * @brief Deseralisation wrapper: specialization for strings */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &, tl::Heap *heap) @@ -589,7 +589,7 @@ PyObject *object_from_variant (const tl::Variant &var, PYAObjectBase *self, cons /** * @brief Deseralisation wrapper: specialization for variants */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase *self, const gsi::ArgType &atype, tl::Heap *heap) @@ -612,7 +612,7 @@ struct reader /** * @brief Deseralisation wrapper: specialization for vectors */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &atype, tl::Heap *heap) @@ -624,7 +624,7 @@ struct reader *ret = PyList_New (0); tl_assert (atype.inner () != 0); PythonBasedVectorAdaptor t (*ret, atype.inner ()); - a->copy_to (&t, *heap); + a->copy_to (&t, *heap); } } }; @@ -632,7 +632,7 @@ struct reader /** * @brief Deseralisation wrapper: specialization for maps */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &atype, tl::Heap *heap) @@ -651,9 +651,9 @@ struct reader }; /** - * @brief Deseralisation wrapper: specialization for object + * @brief Deseralisation wrapper: specialization for object */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase *self, const gsi::ArgType &atype, tl::Heap *heap) @@ -670,7 +670,7 @@ struct reader /** * @brief Deseralisation wrapper: specialization for void */ -template <> +template <> struct reader { void operator() (gsi::SerialArgs *, PythonRef *, PYAObjectBase *, const gsi::ArgType &, tl::Heap *) @@ -679,7 +679,7 @@ struct reader } }; -PythonRef +PythonRef pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap) { PythonRef ret; @@ -701,7 +701,7 @@ tl::Variant PythonBasedVariantAdaptor::var () const return python2c (m_var.get ()); } -void PythonBasedVariantAdaptor::set (const tl::Variant & /*v*/) +void PythonBasedVariantAdaptor::set (const tl::Variant & /*v*/) { // TODO: is there a setter for a string? } @@ -715,7 +715,7 @@ PythonBasedVectorAdaptorIterator::PythonBasedVectorAdaptorIterator (const Python // .. nothing yet .. } -void PythonBasedVectorAdaptorIterator::get (gsi::SerialArgs &w, tl::Heap &heap) const +void PythonBasedVectorAdaptorIterator::get (gsi::SerialArgs &w, tl::Heap &heap) const { PyObject *member = NULL; if (PyTuple_Check (m_array.get ())) { @@ -726,12 +726,12 @@ void PythonBasedVectorAdaptorIterator::get (gsi::SerialArgs &w, tl::Heap &heap) gsi::do_on_type () (mp_ainner->type (), &w, member, *mp_ainner, &heap); } -bool PythonBasedVectorAdaptorIterator::at_end () const +bool PythonBasedVectorAdaptorIterator::at_end () const { return m_i == m_len; } -void PythonBasedVectorAdaptorIterator::inc () +void PythonBasedVectorAdaptorIterator::inc () { ++m_i; } @@ -750,7 +750,7 @@ gsi::VectorAdaptorIterator *PythonBasedVectorAdaptor::create_iterator () const return new PythonBasedVectorAdaptorIterator (m_array, size (), mp_ainner); } -void PythonBasedVectorAdaptor::push (gsi::SerialArgs &r, tl::Heap &heap) +void PythonBasedVectorAdaptor::push (gsi::SerialArgs &r, tl::Heap &heap) { if (PyList_Check (m_array.get ())) { PythonRef member; @@ -761,7 +761,7 @@ void PythonBasedVectorAdaptor::push (gsi::SerialArgs &r, tl::Heap &heap) } } -void PythonBasedVectorAdaptor::clear () +void PythonBasedVectorAdaptor::clear () { if (PySequence_Check (m_array.get ())) { PySequence_DelSlice (m_array.get (), 0, PySequence_Length (m_array.get ())); @@ -777,7 +777,7 @@ size_t PythonBasedVectorAdaptor::size () const } } -size_t PythonBasedVectorAdaptor::serial_size () const +size_t PythonBasedVectorAdaptor::serial_size () const { return mp_ainner->size (); } @@ -793,18 +793,18 @@ PythonBasedMapAdaptorIterator::PythonBasedMapAdaptorIterator (const PythonPtr &h inc (); } -void PythonBasedMapAdaptorIterator::get (gsi::SerialArgs &w, tl::Heap &heap) const +void PythonBasedMapAdaptorIterator::get (gsi::SerialArgs &w, tl::Heap &heap) const { gsi::do_on_type () (mp_ainner_k->type (), &w, m_key, *mp_ainner_k, &heap); gsi::do_on_type () (mp_ainner->type (), &w, m_value, *mp_ainner, &heap); } -bool PythonBasedMapAdaptorIterator::at_end () const +bool PythonBasedMapAdaptorIterator::at_end () const { return ! m_has_items; } -void PythonBasedMapAdaptorIterator::inc () +void PythonBasedMapAdaptorIterator::inc () { m_has_items = PyDict_Next(m_hash.get (), &m_pos, &m_key, &m_value); } @@ -813,7 +813,7 @@ void PythonBasedMapAdaptorIterator::inc () // PythonBasedMapAdaptor implementation PythonBasedMapAdaptor::PythonBasedMapAdaptor (const PythonPtr &hash, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k) - : mp_ainner (ainner), mp_ainner_k (ainner_k), m_hash (hash) + : mp_ainner (ainner), mp_ainner_k (ainner_k), m_hash (hash) { } @@ -822,7 +822,7 @@ gsi::MapAdaptorIterator *PythonBasedMapAdaptor::create_iterator () const return new PythonBasedMapAdaptorIterator (m_hash, mp_ainner, mp_ainner_k); } -void PythonBasedMapAdaptor::insert (gsi::SerialArgs &r, tl::Heap &heap) +void PythonBasedMapAdaptor::insert (gsi::SerialArgs &r, tl::Heap &heap) { PythonRef k, v; gsi::do_on_type () (mp_ainner_k->type (), &r, &k, (PYAObjectBase *) 0, *mp_ainner_k, &heap); @@ -830,7 +830,7 @@ void PythonBasedMapAdaptor::insert (gsi::SerialArgs &r, tl::Heap &heap) PyDict_SetItem (m_hash.get (), k.get (), v.get ()); } -void PythonBasedMapAdaptor::clear () +void PythonBasedMapAdaptor::clear () { PyDict_Clear (m_hash.get ()); } @@ -840,7 +840,7 @@ size_t PythonBasedMapAdaptor::size () const return PyDict_Size (m_hash.get ()); } -size_t PythonBasedMapAdaptor::serial_size () const +size_t PythonBasedMapAdaptor::serial_size () const { return mp_ainner_k->size () + mp_ainner->size (); } @@ -876,7 +876,7 @@ struct test_arg_func if (atype.is_ptr () || atype.is_ref ()) { // check if we have a boxed type - const gsi::ClassBase *cls_decl = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (arg)); + const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg)); if (cls_decl) { const gsi::ClassBase *bc = gsi::cls_decl (); if (cls_decl->is_derived_from (bc)) { @@ -914,11 +914,11 @@ struct test_arg_func #if PY_MAJOR_VERSION < 3 if (PyString_Check (arg)) { *ret = true; - } else + } else #else if (PyBytes_Check (arg)) { *ret = true; - } else + } else #endif if (PyUnicode_Check (arg)) { *ret = true; @@ -981,7 +981,7 @@ struct test_arg_func const gsi::ArgType &ainner = *atype.inner (); const gsi::ArgType &ainner_k = *atype.inner (); - // Note: we test key and value separately. That way we don't need to + // Note: we test key and value separately. That way we don't need to // instantiate a 2d template with do_on_type2. *ret = true; @@ -1010,7 +1010,7 @@ struct test_arg_func return; } - const gsi::ClassBase *cls_decl = PythonInterpreter::instance ()->cls_for_type (Py_TYPE (arg)); + const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg)); if (! cls_decl) { *ret = false; return; @@ -1037,6 +1037,5 @@ test_arg (const gsi::ArgType &atype, PyObject *arg, bool loose) gsi::do_on_type () (atype.type (), &ret, arg, atype, loose); return ret; } - -} +} diff --git a/src/tl/tl/tl.pro b/src/tl/tl/tl.pro index b0ab6a2f7..2621960ec 100644 --- a/src/tl/tl/tl.pro +++ b/src/tl/tl/tl.pro @@ -102,7 +102,8 @@ HEADERS = \ tlUnitTest.h \ tlInt128Support.h \ tlHttpStreamCurl.h \ - tlHttpStreamQt.h + tlHttpStreamQt.h \ + tlDefs.h INCLUDEPATH = DEPENDPATH = diff --git a/src/tl/tl/tlDefs.h b/src/tl/tl/tlDefs.h new file mode 100644 index 000000000..4197cb53b --- /dev/null +++ b/src/tl/tl/tlDefs.h @@ -0,0 +1,57 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if !defined(HDR_tlDefs_h) +# define HDR_tlDefs_h + +// templates provided for building the external symbol +// declarations per library + +# if defined _WIN32 || defined __CYGWIN__ + +# define DEF_INSIDE_PUBLIC __declspec(dllexport) +# define DEF_INSIDE_LOCAL +# define DEF_INSIDE_PUBLIC_TEMPLATE + +# define DEF_OUTSIDE_PUBLIC __declspec(dllimport) +# define DEF_OUTSIDE_LOCAL +# define DEF_OUTSIDE_PUBLIC_TEMPLATE + +# else + +# if __GNUC__ >= 4 || defined(__clang__) +# define DEF_INSIDE_PUBLIC __attribute__ ((visibility ("default"))) +# define DEF_INSIDE_PUBLIC_TEMPLATE __attribute__ ((visibility ("default"))) +# define DEF_INSIDE_LOCAL __attribute__ ((visibility ("hidden"))) +# else +# define DEF_INSIDE_PUBLIC +# define DEF_INSIDE_PUBLIC_TEMPLATE +# define DEF_INSIDE_LOCAL +# endif + +# define DEF_OUTSIDE_PUBLIC DEF_INSIDE_PUBLIC +# define DEF_OUTSIDE_PUBLIC_TEMPLATE DEF_INSIDE_PUBLIC_TEMPLATE +# define DEF_OUTSIDE_LOCAL DEF_INSIDE_LOCAL + +# endif + +#endif diff --git a/testdata/python/basic.py b/testdata/python/basic.py index e458959e0..2bf3dbac1 100644 --- a/testdata/python/basic.py +++ b/testdata/python/basic.py @@ -25,6 +25,7 @@ import gc # Set this to True to disable some tests involving exceptions leak_check = "TEST_LEAK_CHECK" in os.environ +print("@@@1") # see test_21 class AEXT(pya.A): def __init__(self): @@ -134,16 +135,23 @@ class BasicTest(unittest.TestCase): def test_00(self): + print("@@@00") # all references of PA are released now: ac0 = pya.A.a0() + print("@@@00-1") a = pya.A.new_a(100) self.assertEqual( pya.A.a0(), ac0 + 1 ) + print("@@@00-2") a = pya.A() + print("@@@00-31") self.assertEqual(a.a1(), 17) + print("@@@00-32") a.assign(pya.A(110)) + print("@@@00-33") self.assertEqual(a.a1(), 110) + print("@@@00-3") a = None self.assertEqual( pya.A.a0(), ac0 ) @@ -384,6 +392,7 @@ class BasicTest(unittest.TestCase): def test_12(self): + print("@@@12") a1 = pya.A() a1.a5( -15 ) a2 = a1 @@ -584,6 +593,7 @@ class BasicTest(unittest.TestCase): def test_13(self): + print("@@@13") b = pya.B() if not leak_check: @@ -810,6 +820,7 @@ class BasicTest(unittest.TestCase): def test_13b(self): + print("@@@13b") b = pya.B() bb = pya.B() @@ -913,6 +924,7 @@ class BasicTest(unittest.TestCase): def test_14(self): + print("@@@14") a = pya.A() a.a5( 22 ) @@ -926,6 +938,7 @@ class BasicTest(unittest.TestCase): def test_15(self): + print("@@@15") a = pya.A_NC() self.assertEqual( True, isinstance(a, pya.A) ) a.a5( 22 ) @@ -940,6 +953,7 @@ class BasicTest(unittest.TestCase): def test_16(self): + print("@@@16") if leak_check: return @@ -966,6 +980,7 @@ class BasicTest(unittest.TestCase): def test_17(self): + print("@@@17") # test copies of objects being returned b = pya.B() @@ -991,6 +1006,7 @@ class BasicTest(unittest.TestCase): def test_18(self): + print("@@@18") # Test references to objects (returned by b.b7) b = pya.B() @@ -1017,6 +1033,7 @@ class BasicTest(unittest.TestCase): def test_19(self): + print("@@@19") c0 = pya.C() self.assertEqual( c0.g("x"), 1977 ); @@ -1062,6 +1079,7 @@ class BasicTest(unittest.TestCase): def test_20(self): + print("@@@20") b = pya.B() a1 = b.b14a( True ) @@ -1081,6 +1099,7 @@ class BasicTest(unittest.TestCase): def test_21(self): + print("@@@21") # Python does not allow extending built-in types - the following test # is taken from the Ruby binding. I don't know how to implement it for # Python however. @@ -1112,6 +1131,7 @@ class BasicTest(unittest.TestCase): def test_22(self): + print("@@@22") # test client data binding to C++ objects b = pya.B() @@ -1230,6 +1250,7 @@ class BasicTest(unittest.TestCase): def test_23(self): + print("@@@23") b = pya.B() a = pya.A() @@ -1277,6 +1298,7 @@ class BasicTest(unittest.TestCase): def test_24(self): + print("@@@24") n = [ 0, 0 , "" ] # Events @@ -1362,6 +1384,7 @@ class BasicTest(unittest.TestCase): def test_25(self): + print("@@@25") # destruction of an instance via c++ pya.A.a20(None) ac0 = pya.A.a0() @@ -1414,6 +1437,7 @@ class BasicTest(unittest.TestCase): def test_26(self): + print("@@@26") # cyclic references - event bound to itself base_count = EEXT.inst_count() @@ -1457,6 +1481,7 @@ class BasicTest(unittest.TestCase): def test_27(self): + print("@@@27") # destruction of an instance via c++ pya.A.a20(None) ac0 = pya.A.a0() @@ -1493,6 +1518,7 @@ class BasicTest(unittest.TestCase): def test_28(self): + print("@@@28") self.assertEqual(pya.B.inst() == None, True) self.assertEqual(pya.B.has_inst(), False) @@ -1528,6 +1554,7 @@ class BasicTest(unittest.TestCase): def test_30(self): + print("@@@30") # some basic tests for the *Value boxing classes val = pya.Value() @@ -1704,6 +1731,7 @@ class BasicTest(unittest.TestCase): def test_31(self): + print("@@@31") # some basic tests with derived and base classes pya.X.init() @@ -1760,6 +1788,7 @@ class BasicTest(unittest.TestCase): def test_32(self): + print("@@@32") # run test only if we have Qt bindings if not "QStringPair" in pya.__dict__: return @@ -1788,6 +1817,7 @@ class BasicTest(unittest.TestCase): def test_33(self): + print("@@@33") def str_from_bytearray(ba): if (sys.version_info > (3, 0)): return ba @@ -1822,6 +1852,7 @@ class BasicTest(unittest.TestCase): def test_34(self): + print("@@@34") # run test only if we have Qt bindings if not "QDialog" in pya.__dict__: return @@ -1840,6 +1871,7 @@ class BasicTest(unittest.TestCase): def test_35(self): + print("@@@35") # vectors of pointers pya.X.init() @@ -1940,12 +1972,14 @@ class BasicTest(unittest.TestCase): def test_36(self): + print("@@@36") x = XEdge() self.assertEqual("XEdge", type(x).__name__) self.assertEqual("(1,2;3,4)", str(x)) def test_37(self): + print("@@@37") # This test is taken from the Ruby binding, but # Python does not have protected methods: return @@ -1965,6 +1999,7 @@ class BasicTest(unittest.TestCase): def test_38(self): + print("@@@38") # mixed const / non-const reference and events ec = pya.E.ic() self.assertEqual(ec.is_const_object(), True) @@ -1996,6 +2031,7 @@ class BasicTest(unittest.TestCase): def test_39(self): + print("@@@39") # mixed const / non-const reference and events fc = pya.F.ic() self.assertEqual(fc.is_const_object(), True) @@ -2026,6 +2062,7 @@ class BasicTest(unittest.TestCase): def test_40(self): + print("@@@40") # optional arguments g = pya.G() @@ -2109,6 +2146,7 @@ class BasicTest(unittest.TestCase): def test_41(self): + print("@@@41") # maps b = pya.B() @@ -2170,6 +2208,7 @@ class BasicTest(unittest.TestCase): def test_42(self): + print("@@@42") # virtual functions and sub-classes z = pya.Z() self.assertEqual(z.f(None), "(nil)") @@ -2198,6 +2237,7 @@ class BasicTest(unittest.TestCase): def test_50(self): + print("@@@50") # advanced containers and out parameters b = pya.B() @@ -2372,6 +2412,7 @@ class BasicTest(unittest.TestCase): self.assertEqual(b.qhash_is(), {}) def test_51(self): + print("@@@51") # new subclass and child class declarations y2 = pya.Y2() @@ -2389,6 +2430,7 @@ class BasicTest(unittest.TestCase): self.assertEqual(isinstance(y4, pya.X), False) def test_60(self): + print("@@@60") class SignalCollector(object): @@ -2482,6 +2524,7 @@ class BasicTest(unittest.TestCase): self.assertEqual(sc.got_s2_2, None) def test_61(self): + print("@@@61") class SignalCollector(object): @@ -2565,6 +2608,7 @@ class BasicTest(unittest.TestCase): self.assertEqual(sc.got_s0b, 0) def test_70(self): + print("@@@70") class SignalCollector(object): @@ -2613,6 +2657,7 @@ class BasicTest(unittest.TestCase): self.assertEqual(sc.got_s2_2.tag, 111) def test_71(self): + print("@@@71") class SignalCollector(object): @@ -2697,6 +2742,7 @@ class BasicTest(unittest.TestCase): # Custom factory implemented in Python def test_80(self): + print("@@@80") gc = pya.GObject.g_inst_count() gf = PyGFactory() go = pya.GFactory.create_f(gf, 17) @@ -2706,6 +2752,7 @@ class BasicTest(unittest.TestCase): go = None self.assertEqual(pya.GObject.g_inst_count(), gc) +print("@@@2") # run unit tests if __name__ == '__main__': suite = unittest.TestSuite() @@ -2715,4 +2762,5 @@ if __name__ == '__main__': if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): sys.exit(1) +print("@@@3")