10000 Merge pull request #21478 from VadimLevin:dev/vlevin/pysubmodules-ini… · opencv/opencv@8f4473b · GitHub
[go: up one dir, main page]

Skip to content

Commit 8f4473b

Browse files
committed
Merge pull request #21478 from VadimLevin:dev/vlevin/pysubmodules-initialization-fix
2 parents 25f2527 + eca2d92 commit 8f4473b

File tree

3 files changed

+189
-41
lines changed

3 files changed

+189
-41
lines changed

modules/core/include/opencv2/core/bindings_utils.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ AsyncArray testAsyncException()
213213
return p.getArrayResult();
214214
}
215215

216+
namespace nested {
217+
CV_WRAP static inline bool testEchoBooleanFunction(bool flag) {
218+
return flag;
219+
}
220+
} // namespace nested
221+
216222
//! @} // core_utils
217223
} // namespace cv::utils
218224

modules/python/src2/cv2.cpp

Lines changed: 168 additions & 41 deletions
+
name.c_str()
Original file line numberDiff line numberDiff line change
@@ -2100,53 +2100,162 @@ struct ConstDef
21002100
long long val;
21012101
};
21022102

2103-
static void init_submodule(PyObject * root, const char * name, PyMethodDef * methods, ConstDef * consts)
2104-
{
2105-
// traverse and create nested submodules
2106-
std::string s = name;
2107-
size_t i = s.find('.');
2108-
while (i < s.length() && i != std::string::npos)
2109-
{
2110-
size_t j = s.find('.', i);
2111-
if (j == std::string::npos)
2112-
j = s.length();
2113-
std::string short_name = s.substr(i, j-i);
2114-
std::string full_name = s.substr(0, j);
2115-
i = j+1;
2116-
2117-
PyObject * d = PyModule_GetDict(root);
2118-
PyObject * submod = PyDict_GetItemString(d, short_name.c_str());
2119-
if (submod == NULL)
2120-
{
2121-
submod = PyImport_AddModule(full_name.c_str());
2122-
PyDict_SetItemString(d, short_name.c_str(), submod);
2123-
}
2124-
2125-
if (short_name != "")
2126-
root = submod;
2127-
}
2128-
2129-
// populate module's dict
2130-
PyObject * d = PyModule_GetDict(root);
2131-
for (PyMethodDef * m = methods; m->ml_name != NULL; ++m)
2132-
{
2133-
PyObject * method_obj = PyCFunction_NewEx(m, NULL, NULL);
2134-
PyDict_SetItemString(d, m->ml_name, method_obj);
2135-
Py_DECREF(method_obj);
2136-
}
2137-
for (ConstDef * c = consts; c->name != NULL; ++c)
2138-
{
2139-
PyDict_SetItemString(d, c->name, PyLong_FromLongLong(c->val));
2140-
}
2103+
static inline bool strStartsWith(const std::string& str, const std::string& prefix) {
2104+
return prefix.empty() || \
2105+
(str.size() >= prefix.size() && std::memcmp(str.data(), prefix.data(), prefix.size()) == 0);
2106+
}
2107+
2108+
static inline bool strEndsWith(const std::string& str, char symbol) {
2109+
return !str.empty() && str[str.size() - 1] == symbol;
2110+
}
2111+
2112+
/**
2113+
* \brief Creates a submodule of the `root`. Missing parents submodules
2114+
* are created as needed. If name equals to parent module name than
2115+
* borrowed reference to parent module is returned (no reference counting
2116+
* are done).
2117+
* Submodule lifetime is managed by the parent module.
2118+
* If nested submodules are created than the lifetime is managed by the
2119+
* predecessor submodule in a list.
2120+
*
2121+
* \param parent_module Parent module object.
2122+
* \param name Submodule name.
2123+
* \return borrowed reference to the created submodule.
2124+
* If any of submodules can't be created than NULL is returned.
2125+
*/
2126+
static PyObject* createSubmodule(PyObject* parent_module, const std::string& name)
2127+
{
2128+
if (!parent_module)
2129+
{
2130+
return PyErr_Format(PyExc_ImportError,
2131+
"Bindings generation error. "
2132+
"Parent module is NULL during the submodule '%s' creation",
2133+
name.c_str()
2134+
);
2135+
}
2136+
if (strEndsWith(name, '.'))
2137+
{
2138+
return PyErr_Format(PyExc_ImportError,
2139+
"Bindings generation error. "
2140+
"Submodule can't end with a dot. Got: %s", name.c_str()
2141+
);
2142+
}
2143+
2144+
const std::string parent_name = PyModule_GetName(parent_module);
2145+
2146+
/// Special case handling when caller tries to register a submodule of the parent module with
2147+
/// the same name
2148+
if (name == parent_name) {
2149+
return parent_module;
2150+
}
2151+
2152+
if (!strStartsWith(name, parent_name))
2153+
{
2154+
return PyErr_Format(PyExc_ImportError,
2155+
"Bindings generation error. "
2156+
"Submodule name should always start with a parent module name. "
2157+
"Parent name: %s. Submodule name: %s", parent_name.c_str(),
2158
2159+
);
2160+
}
2161+
2162+
size_t submodule_name_end = name.find('.', parent_name.size() + 1);
2163+
/// There is no intermediate submodules in the provided name
2164+
if (submodule_name_end == std::string::npos)
2165+
{
2166+
submodule_name_end = name.size();
2167+
}
2168+
2169+
PyObject* submodule = parent_module;
2170+
2171+
for (size_t submodule_name_start = parent_name.size() + 1;
2172+
submodule_name_start < name.size(); )
2173+
{
2174+
const std::string submodule_name = name.substr(submodule_name_start,
2175+
submodule_name_end - submodule_name_start);
2176+
2177+
const std::string full_submodule_name = name.substr(0, submodule_name_end);
2178+
2179+
2180+
PyObject* parent_module_dict = PyModule_GetDict(submodule);
2181+
/// If submodule already exists it can be found in the parent module dictionary,
2182+
/// otherwise it should be added to it.
2183+
submodule = PyDict_GetItemString(parent_module_dict,
2184+
submodule_name.c_str());
2185+
if (!submodule)
2186+
{
2187+
submodule = PyImport_AddModule(full_submodule_name.c_str());
2188+
if (PyDict_SetItemString(parent_module_dict, submodule_name.c_str(), submodule) < 0) {
2189+
Py_CLEAR(submodule);
2190+
return PyErr_Format(PyExc_ImportError,
2191+
"Can't register a submodule '%s' (full name: '%s')",
2192+
submodule_name.c_str(), full_submodule_name.c_str()
2193+
);
2194+
}
2195+
/// PyDict_SetItemString doesn't steal a reference so the reference counter
2196+
/// of the submodule should be decremented to bind submodule lifetime to the
2197+
/// parent module
2198+
Py_DECREF(submodule);
2199+
}
21412200

2201+
submodule_name_start = submodule_name_end + 1;
2202+
2203+
submodule_name_end = name.find('.', submodule_name_start);
2204+
if (submodule_name_end == std::string::npos) {
2205+
submodule_name_end = name.size();
2206+
}
2207+
}
2208+
return submodule;
2209+
}
2210+
2211+
static bool init_submodule(PyObject * root, const char * name, PyMethodDef * methods, ConstDef * consts)
2212+
{
2213+
// traverse and create nested submodules
2214+
PyObject* submodule = createSubmodule(root, name);
2215+
if (!submodule)
2216+
{
2217+
return false;
2218+
}
2219+
// populate module's dict
2220+
PyObject * d = PyModule_GetDict(submodule);
2221+
for (PyMethodDef * m = methods; m->ml_name != NULL; ++m)
2222+
{
2223+
PyObject * method_obj = PyCFunction_NewEx(m, NULL, NULL);
2224+
if (PyDict_SetItemString(d, m->ml_name, method_obj) < 0)
2225+
{
2226+
PyErr_Format(PyExc_ImportError,
2227+
"Can't register function %s in module: %s", m->ml_name, name
2228+
);
2229+
Py_CLEAR(method_obj);
2230+
return false;
2231+
}
2232+
Py_DECREF(method_obj);
2233+
}
2234+
for (ConstDef * c = consts; c->name != NULL; ++c)
2235+
{
2236+
PyObject* const_obj = PyLong_FromLongLong(c->val);
2237+
if (PyDict_SetItemString(d, c->name, const_obj) < 0)
2238+
{
2239+
PyErr_Format(PyExc_ImportError,
2240+
"Can't register constant %s in module %s", c->name, name
2241+
);
2242+
Py_CLEAR(const_obj);
2243+
return false;
2244+
}
2245+
Py_DECREF(const_obj);
2246+
}
2247+
return true;
21422248
}
21432249

21442250
#include "pyopencv_generated_modules_content.h"
21452251

21462252
static bool init_body(PyObject * m)
21472253
{
21482254
#define CVPY_MODULE(NAMESTR, NAME) \
2149-
init_submodule(m, MODULESTR NAMESTR, methods_##NAME, consts_##NAME)
2255+
if (!init_submodule(m, MODULESTR NAMESTR, methods_##NAME, consts_##NAME)) \
2256+
{ \
2257+
return false; \
2258+
}
21502259
#include "pyopencv_generated_modules.h"
21512260
#undef CVPY_MODULE
21522261

@@ -2163,7 +2272,13 @@ static bool init_body(PyObject * m)
21632272
PyObject* d = PyModule_GetDict(m);
21642273

21652274

2166-
PyDict_SetItemString(d, "__version__", PyString_FromString(CV_VERSION));
2275+
PyObject* version_obj = PyString_FromString(CV_VERSION);
2276+
if (PyDict_SetItemString(d, "__version__", version_obj) < 0) {
2277+
PyErr_SetString(PyExc_ImportError, "Can't update module version");
2278+
Py_CLEAR(version_obj);
2279+
return false;
2280+
}
2281+
Py_DECREF(version_obj);
21672282

21682283
PyObject *opencv_error_dict = PyDict_New();
21692284
PyDict_SetItemString(opencv_error_dict, "file", Py_None);
@@ -2177,7 +2292,18 @@ static bool init_body(PyObject * m)
21772292
PyDict_SetItemString(d, "error", opencv_error);
21782293

21792294

2180-
#define PUBLISH(I) PyDict_SetItemString(d, #I, PyInt_FromLong(I))
2295+
#define PUBLISH_(I, var_name, type_obj) \
2296+
PyObject* type_obj = PyInt_FromLong(I); \
2297+
if (PyDict_SetItemString(d, var_name, type_obj) < 0) \
2298+
{ \
2299+
PyErr_SetString(PyExc_ImportError, "Can't register " var_name " constant"); \
2300+
Py_CLEAR(type_obj); \
2301+
return false; \
2302+
} \
2303+
Py_DECREF(type_obj);
2304+
2305+
#define PUBLISH(I) PUBLISH_(I, #I, I ## _obj)
2306+
21812307
PUBLISH(CV_8U);
21822308
PUBLISH(CV_8UC1);
21832309
PUBLISH(CV_8UC2);
@@ -2213,6 +2339,7 @@ static bool init_body(PyObject * m)
22132339
PUBLISH(CV_64FC2);
22142340
PUBLISH(CV_64FC3);
22152341
PUBLISH(CV_64FC4);
2342+
#undef PUBLISH_
22162343
#undef PUBLISH
22172344

22182345
return true;

modules/python/test/test_misc.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
from __future__ import print_function
33

4+
import sys
45
import ctypes
56
from functools import partial
67
from collections import namedtuple
@@ -596,6 +597,20 @@ def test_result_rotated_rect_issue_20930(self):
596597
self.assertTrue(isinstance(rr, tuple), msg=type(rrv))
597598
self.assertEqual(len(rr), 3)
598599

600+
def test_nested_function_availability(self):
601+
self.assertTrue(hasattr(cv.utils, "nested"),
602+
msg="Module is not generated for nested namespace")
603+
self.assertTrue(hasattr(cv.utils.nested, "testEchoBooleanFunction"),
604+
msg="Function in nested module is not available")
605+
self.assertEqual(sys.getrefcount(cv.utils.nested), 2,
606+
msg="Nested submodule lifetime should be managed by "
607+
"the parent module so the reference count should be "
608+
"2, because `getrefcount` temporary increases it.")
609+
for flag in (True, False):
610+
self.assertEqual(flag, cv.utils.nested.testEchoBooleanFunction(flag),
611+
msg="Function in nested module returns wrong result")
612+
613+
599614
class SamplesFindFile(NewOpenCVTests):
600615

601616
def test_ExistedFile(self):

0 commit comments

Comments
 (0)
0