chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:06:04 +08:00
commit 86c9b1c39f
7743 changed files with 3316339 additions and 0 deletions
@@ -0,0 +1,62 @@
import argparse
import warnings
import os
import sys
if sys.version_info >= (3, 8, ):
# shutil.copytree received the `dirs_exist_ok` parameter
from functools import partial
import shutil
copy_tree = partial(shutil.copytree, dirs_exist_ok=True)
else:
from distutils.dir_util import copy_tree
def _remove_stale_pyi_files(directory):
"""Remove .pyi files and py.typed markers from the directory tree.
During incremental builds, disabling a previously enabled module leaves
stale typing stubs in the loader directory from a previous copy. Since
copy_tree merges rather than replaces, those stale files persist.
Removing all stub files before copying ensures only stubs for currently
enabled modules are present. Runtime .py files are not affected.
"""
for dirpath, dirnames, filenames in os.walk(directory):
for fname in filenames:
if fname.endswith('.pyi') or fname == 'py.typed':
os.remove(os.path.join(dirpath, fname))
def main():
args = parse_arguments()
py_typed_path = os.path.join(args.stubs_dir, 'py.typed')
if not os.path.isfile(py_typed_path):
warnings.warn(
'{} is missing, it means that typings stubs generation is either '
'failed or has been skipped. Ensure that Python 3.6+ is used for '
'build and there is no warnings during Python source code '
'generation phase.'.format(py_typed_path)
)
return
if os.path.isdir(args.output_dir):
_remove_stale_pyi_files(args.output_dir)
copy_tree(args.stubs_dir, args.output_dir)
def parse_arguments():
parser = argparse.ArgumentParser(
description='Copies generated typing stubs only when generation '
'succeeded. This is identified by presence of the `py.typed` file '
'inside typing stubs directory.'
)
parser.add_argument('--stubs_dir', type=str,
help='Path to directory containing generated typing '
'stubs file')
parser.add_argument('--output_dir', type=str,
help='Path to output directory')
return parser.parse_args()
if __name__ == '__main__':
main()
+611
View File
@@ -0,0 +1,611 @@
// must be defined before importing numpy headers
// https://numpy.org/doc/1.17/reference/c-api.array.html#importing-the-api
#define PY_ARRAY_UNIQUE_SYMBOL opencv_ARRAY_API
#include "cv2.hpp"
#include "opencv2/opencv_modules.hpp"
#include "opencv2/core.hpp"
#include "opencv2/core/utils/logger.hpp"
#include "pyopencv_generated_include.h"
#include "opencv2/core/types_c.h"
#include "cv2_util.hpp"
#include "cv2_numpy.hpp"
#include "cv2_convert.hpp"
#include "cv2_highgui.hpp"
using namespace cv;
typedef std::vector<uchar> vector_uchar;
typedef std::vector<char> vector_char;
typedef std::vector<int> vector_int;
typedef std::vector<float> vector_float;
typedef std::vector<double> vector_double;
typedef std::vector<size_t> vector_size_t;
typedef std::vector<Point> vector_Point;
typedef std::vector<Point2f> vector_Point2f;
typedef std::vector<Point3f> vector_Point3f;
typedef std::vector<Size> vector_Size;
typedef std::vector<Vec2f> vector_Vec2f;
typedef std::vector<Vec3f> vector_Vec3f;
typedef std::vector<Vec4f> vector_Vec4f;
typedef std::vector<Vec6f> vector_Vec6f;
typedef std::vector<Vec4i> vector_Vec4i;
typedef std::vector<Rect> vector_Rect;
typedef std::vector<Rect2d> vector_Rect2d;
typedef std::vector<RotatedRect> vector_RotatedRect;
typedef std::vector<KeyPoint> vector_KeyPoint;
typedef std::vector<Mat> vector_Mat;
typedef std::vector<std::vector<Mat> > vector_vector_Mat;
typedef std::vector<UMat> vector_UMat;
typedef std::vector<DMatch> vector_DMatch;
typedef std::vector<String> vector_String;
typedef std::vector<std::string> vector_string;
typedef std::vector<Scalar> vector_Scalar;
#ifdef HAVE_OPENCV_OBJDETECT
typedef std::vector<aruco::Dictionary> vector_Dictionary;
#endif // HAVE_OPENCV_OBJDETECT
typedef std::vector<std::vector<char> > vector_vector_char;
typedef std::vector<std::vector<Point> > vector_vector_Point;
typedef std::vector<std::vector<Point2f> > vector_vector_Point2f;
typedef std::vector<std::vector<Point3f> > vector_vector_Point3f;
typedef std::vector<std::vector<DMatch> > vector_vector_DMatch;
typedef std::vector<std::vector<KeyPoint> > vector_vector_KeyPoint;
// enum { ARG_NONE = 0, ARG_MAT = 1, ARG_SCALAR = 2 };
///////////////////////////////////////////////////////////////////////////////////////
static int convert_to_char(PyObject *o, char *dst, const ArgInfo& info)
{
std::string str;
if (getUnicodeString(o, str))
{
*dst = str[0];
return 1;
}
(*dst) = 0;
return failmsg("Expected single character string for argument '%s'", info.name);
}
#ifdef __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
#include "pyopencv_generated_enums.h"
#ifdef CVPY_DYNAMIC_INIT
#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, _1, _2, SCOPE) CVPY_TYPE_DECLARE_DYNAMIC(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE)
#else
#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, _1, _2, SCOPE) CVPY_TYPE_DECLARE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE)
#endif
#include "pyopencv_generated_types.h"
#undef CVPY_TYPE
#include "pyopencv_custom_headers.h"
#include "pyopencv_generated_types_content.h"
#include "pyopencv_generated_funcs.h"
static PyObject* pycvRegisterMatType(PyObject *self, PyObject *value)
{
CV_LOG_DEBUG(NULL, cv::format("pycvRegisterMatType %p %p\n", self, value));
if (0 == PyType_Check(value))
{
PyErr_SetString(PyExc_TypeError, "Type argument is expected");
return NULL;
}
Py_INCREF(value);
pyopencv_Mat_TypePtr = (PyTypeObject*)value;
Py_RETURN_NONE;
}
static PyMethodDef special_methods[] = {
{"_registerMatType", (PyCFunction)(pycvRegisterMatType), METH_O, "_registerMatType(cv.Mat) -> None (Internal)"},
{"redirectError", CV_PY_FN_WITH_KW(pycvRedirectError), "redirectError(onError) -> None"},
#ifdef HAVE_OPENCV_HIGHGUI
{"createTrackbar", (PyCFunction)pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"},
{"createButton", CV_PY_FN_WITH_KW(pycvCreateButton), "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"},
{"setMouseCallback", CV_PY_FN_WITH_KW(pycvSetMouseCallback), "setMouseCallback(windowName, onMouse [, param]) -> None"},
#endif
#ifdef HAVE_OPENCV_DNN
{"dnn_registerLayer", CV_PY_FN_WITH_KW(pyopencv_cv_dnn_registerLayer), "registerLayer(type, class) -> None"},
{"dnn_unregisterLayer", CV_PY_FN_WITH_KW(pyopencv_cv_dnn_unregisterLayer), "unregisterLayer(type) -> None"},
#endif
{NULL, NULL},
};
/************************************************************************/
/* Module init */
struct ConstDef
{
const char * name;
long long val;
};
static inline bool strStartsWith(const std::string& str, const std::string& prefix) {
return prefix.empty() || \
(str.size() >= prefix.size() && std::memcmp(str.data(), prefix.data(), prefix.size()) == 0);
}
static inline bool strEndsWith(const std::string& str, char symbol) {
return !str.empty() && str[str.size() - 1] == symbol;
}
/**
* \brief Creates a submodule of the `root`. Missing parents submodules
* are created as needed. If name equals to parent module name than
* borrowed reference to parent module is returned (no reference counting
* are done).
* Submodule lifetime is managed by the parent module.
* If nested submodules are created than the lifetime is managed by the
* predecessor submodule in a list.
*
* \param parent_module Parent module object.
* \param name Submodule name.
* \return borrowed reference to the created submodule.
* If any of submodules can't be created than NULL is returned.
*/
static PyObject* createSubmodule(PyObject* parent_module, const std::string& name)
{
if (!parent_module)
{
return PyErr_Format(PyExc_ImportError,
"Bindings generation error. "
"Parent module is NULL during the submodule '%s' creation",
name.c_str()
);
}
if (strEndsWith(name, '.'))
{
return PyErr_Format(PyExc_ImportError,
"Bindings generation error. "
"Submodule can't end with a dot. Got: %s", name.c_str()
);
}
const std::string parent_name = PyModule_GetName(parent_module);
/// Special case handling when caller tries to register a submodule of the parent module with
/// the same name
if (name == parent_name) {
return parent_module;
}
if (!strStartsWith(name, parent_name))
{
return PyErr_Format(PyExc_ImportError,
"Bindings generation error. "
"Submodule name should always start with a parent module name. "
"Parent name: %s. Submodule name: %s", parent_name.c_str(),
name.c_str()
);
}
size_t submodule_name_end = name.find('.', parent_name.size() + 1);
/// There is no intermediate submodules in the provided name
if (submodule_name_end == std::string::npos)
{
submodule_name_end = name.size();
}
PyObject* submodule = parent_module;
for (size_t submodule_name_start = parent_name.size() + 1;
submodule_name_start < name.size(); )
{
const std::string submodule_name = name.substr(submodule_name_start,
submodule_name_end - submodule_name_start);
const std::string full_submodule_name = name.substr(0, submodule_name_end);
PyObject* parent_module_dict = PyModule_GetDict(submodule);
/// If submodule already exists it can be found in the parent module dictionary,
/// otherwise it should be added to it.
submodule = PyDict_GetItemString(parent_module_dict,
submodule_name.c_str());
if (!submodule)
{
/// Populates global modules dictionary and returns borrowed reference to it
submodule = PyImport_AddModule(full_submodule_name.c_str());
if (!submodule)
{
/// Return `PyImport_AddModule` NULL with an exception set on failure.
return NULL;
}
/// Populates parent module dictionary. Submodule lifetime should be managed
/// by the global modules dictionary and parent module dictionary, so Py_DECREF after
/// successful call to the `PyDict_SetItemString` is redundant.
if (PyDict_SetItemString(parent_module_dict, submodule_name.c_str(), submodule) < 0) {
return PyErr_Format(PyExc_ImportError,
"Can't register a submodule '%s' (full name: '%s')",
submodule_name.c_str(), full_submodule_name.c_str()
);
}
}
submodule_name_start = submodule_name_end + 1;
submodule_name_end = name.find('.', submodule_name_start);
if (submodule_name_end == std::string::npos) {
submodule_name_end = name.size();
}
}
return submodule;
}
static bool init_submodule(PyObject * root, const char * name, PyMethodDef * methods, ConstDef * consts)
{
// traverse and create nested submodules
PyObject* submodule = createSubmodule(root, name);
if (!submodule)
{
return false;
}
// populate module's dict
PyObject * d = PyModule_GetDict(submodule);
for (PyMethodDef * m = methods; m->ml_name != NULL; ++m)
{
PyObject * method_obj = PyCFunction_NewEx(m, NULL, NULL);
if (PyDict_SetItemString(d, m->ml_name, method_obj) < 0)
{
PyErr_Format(PyExc_ImportError,
"Can't register function %s in module: %s", m->ml_name, name
);
Py_CLEAR(method_obj);
return false;
}
Py_DECREF(method_obj);
}
for (ConstDef * c = consts; c->name != NULL; ++c)
{
PyObject* const_obj = PyLong_FromLongLong(c->val);
if (PyDict_SetItemString(d, c->name, const_obj) < 0)
{
PyErr_Format(PyExc_ImportError,
"Can't register constant %s in module %s", c->name, name
);
Py_CLEAR(const_obj);
return false;
}
Py_DECREF(const_obj);
}
return true;
}
static inline
bool registerTypeInModuleScope(PyObject* module, const char* type_name, PyObject* type_obj)
{
Py_INCREF(type_obj); /// Give PyModule_AddObject a reference to steal.
if (PyModule_AddObject(module, type_name, type_obj) < 0)
{
PyErr_Format(PyExc_ImportError,
"Failed to register type '%s' in module scope '%s'",
type_name, PyModule_GetName(module)
);
Py_DECREF(type_obj);
return false;
}
return true;
}
static inline
bool registerTypeInClassScope(PyObject* cls, const char* type_name, PyObject* type_obj)
{
if (!PyType_CheckExact(cls)) {
PyErr_Format(PyExc_ImportError,
"Failed to register type '%s' in class scope. "
"Scope class object has a wrong type", type_name
);
return false;
}
if (PyObject_SetAttrString(cls, type_name, type_obj) < 0)
{
#ifndef Py_LIMITED_API
PyObject* cls_dict = reinterpret_cast<PyTypeObject*>(cls)->tp_dict;
if (PyDict_SetItemString(cls_dict, type_name, type_obj) >= 0) {
/// Clearing the error set by PyObject_SetAttrString:
/// TypeError: can't set attributes of built-in/extension type NAME
PyErr_Clear();
return true;
}
#endif
const std::string cls_name = getPyObjectNameAttr(cls);
PyErr_Format(PyExc_ImportError,
"Failed to register type '%s' in '%s' class scope. Can't update scope dictionary",
type_name, cls_name.c_str()
);
return false;
}
return true;
}
static inline
PyObject* getScopeFromTypeObject(PyObject* obj, const std::string& scope_name)
{
if (!PyType_CheckExact(obj)) {
const std::string type_name = getPyObjectNameAttr(obj);
return PyErr_Format(PyExc_ImportError,
"Failed to get scope from type '%s' "
"Scope class object has a wrong type", type_name.c_str()
);
}
/// When using LIMITED API all classes are registered in the heap
#if defined(Py_LIMITED_API)
return PyObject_GetAttrString(obj, scope_name.c_str());
#else
/// Otherwise classes may be registed on the stack or heap
PyObject* type_dict = reinterpret_cast<PyTypeObject*>(obj)->tp_dict;
if (!type_dict) {
const std::string type_name = getPyObjectNameAttr(obj);
return PyErr_Format(PyExc_ImportError,
"Failed to get scope from type '%s' "
"Type dictionary is not available", type_name.c_str()
);
}
return PyDict_GetItemString(type_dict, scope_name.c_str());
#endif // Py_LIMITED_API
}
static inline
PyObject* findTypeScope(PyObject* root_module, const std::string& scope_name)
{
PyObject* scope = root_module;
if (scope_name.empty())
{
return scope;
}
/// Starting with 1 to omit leading dot in the scope name
size_t name_end = scope_name.find('.', 1);
if (name_end == std::string::npos)
{
name_end = scope_name.size();
}
for (size_t name_start = 1; name_start < scope_name.size() && scope; )
{
const std::string current_scope_name = scope_name.substr(name_start,
name_end - name_start);
if (PyModule_CheckExact(scope))
{
PyObject* scope_dict = PyModule_GetDict(scope);
if (!scope_dict)
{
return PyErr_Format(PyExc_ImportError,
"Scope '%s' dictionary is not available during the search for "
" the '%s' scope object", current_scope_name.c_str(),
scope_name.c_str()
);
}
scope = PyDict_GetItemString(scope_dict, current_scope_name.c_str());
}
else if (PyType_CheckExact(scope))
{
scope = getScopeFromTypeObject(scope, current_scope_name);
}
else
{
return PyErr_Format(PyExc_ImportError,
"Can't find scope '%s'. '%s' doesn't reference a module or a class",
scope_name.c_str(), current_scope_name.c_str()
);
}
name_start = name_end + 1;
name_end = scope_name.find('.', name_start);
if (name_end == std::string::npos)
{
name_end = scope_name.size();
}
}
if (!scope)
{
return PyErr_Format(PyExc_ImportError,
"Module or class with name '%s' can't be found in '%s' module",
scope_name.c_str(), PyModule_GetName(root_module)
);
}
return scope;
}
static bool registerNewType(PyObject* root_module, const char* type_name,
PyObject* type_obj, const std::string& scope_name)
{
PyObject* scope = findTypeScope(root_module, scope_name);
/// If scope can't be found it means that there is an error during
/// bindings generation
if (!scope) {
return false;
}
if (PyModule_CheckExact(scope))
{
if (!registerTypeInModuleScope(scope, type_name, type_obj))
{
return false;
}
}
else
{
/// In Python 2 it is disallowed to register an inner classes
/// via modifing dictionary of the built-in type.
if (!registerTypeInClassScope(scope, type_name, type_obj))
{
return false;
}
}
/// Expose all classes that are defined in the submodules as aliases in the
/// root module for backward compatibility
/// If submodule and root module are same than no aliases registration are
/// required
if (scope != root_module)
{
std::string type_name_str(type_name);
std::string alias_name;
alias_name.reserve(scope_name.size() + type_name_str.size());
std::replace_copy(scope_name.begin() + 1, scope_name.end(), std::back_inserter(alias_name), '.', '_');
alias_name += '_';
alias_name += type_name_str;
return registerTypeInModuleScope(root_module, alias_name.c_str(), type_obj);
}
return true;
}
#include "pyopencv_generated_modules_content.h"
static bool init_body(PyObject * m)
{
#define CVPY_MODULE(NAMESTR, NAME) \
if (!init_submodule(m, MODULESTR NAMESTR, methods_##NAME, consts_##NAME)) \
{ \
return false; \
}
#include "pyopencv_generated_modules.h"
#undef CVPY_MODULE
#ifdef CVPY_DYNAMIC_INIT
#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, _1, _2, BASE, CONSTRUCTOR, SCOPE) CVPY_TYPE_INIT_DYNAMIC(EXPORT_NAME, CLASS_ID, return false, BASE, CONSTRUCTOR, SCOPE)
PyObject * pyopencv_NoBase_TypePtr = NULL;
#else
#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, _1, _2, BASE, CONSTRUCTOR, SCOPE) CVPY_TYPE_INIT_STATIC(EXPORT_NAME, CLASS_ID, return false, BASE, CONSTRUCTOR, SCOPE)
PyTypeObject * pyopencv_NoBase_TypePtr = NULL;
#endif
#include "pyopencv_generated_types.h"
#undef CVPY_TYPE
PyObject* d = PyModule_GetDict(m);
PyObject* version_obj = PyString_FromString(CV_VERSION);
if (PyDict_SetItemString(d, "__version__", version_obj) < 0) {
PyErr_SetString(PyExc_ImportError, "Can't update module version");
Py_CLEAR(version_obj);
return false;
}
Py_DECREF(version_obj);
PyObject *opencv_error_dict = PyDict_New();
PyDict_SetItemString(opencv_error_dict, "file", Py_None);
PyDict_SetItemString(opencv_error_dict, "func", Py_None);
PyDict_SetItemString(opencv_error_dict, "line", Py_None);
PyDict_SetItemString(opencv_error_dict, "code", Py_None);
PyDict_SetItemString(opencv_error_dict, "msg", Py_None);
PyDict_SetItemString(opencv_error_dict, "err", Py_None);
opencv_error = PyErr_NewException((char*)MODULESTR".error", NULL, opencv_error_dict);
Py_DECREF(opencv_error_dict);
PyDict_SetItemString(d, "error", opencv_error);
#define PUBLISH_(I, var_name, type_obj) \
PyObject* type_obj = PyInt_FromLong(I); \
if (PyDict_SetItemString(d, var_name, type_obj) < 0) \
{ \
PyErr_SetString(PyExc_ImportError, "Can't register " var_name " constant"); \
Py_CLEAR(type_obj); \
return false; \
} \
Py_DECREF(type_obj);
#define PUBLISH(I) PUBLISH_(I, #I, I ## _obj)
PUBLISH(CV_8U);
PUBLISH(CV_8UC1);
PUBLISH(CV_8UC2);
PUBLISH(CV_8UC3);
PUBLISH(CV_8UC4);
PUBLISH(CV_8S);
PUBLISH(CV_8SC1);
PUBLISH(CV_8SC2);
PUBLISH(CV_8SC3);
PUBLISH(CV_8SC4);
PUBLISH(CV_16U);
PUBLISH(CV_16UC1);
PUBLISH(CV_16UC2);
PUBLISH(CV_16UC3);
PUBLISH(CV_16UC4);
PUBLISH(CV_16S);
PUBLISH(CV_16SC1);
PUBLISH(CV_16SC2);
PUBLISH(CV_16SC3);
PUBLISH(CV_16SC4);
PUBLISH(CV_32S);
PUBLISH(CV_32SC1);
PUBLISH(CV_32SC2);
PUBLISH(CV_32SC3);
PUBLISH(CV_32SC4);
PUBLISH(CV_32F);
PUBLISH(CV_32FC1);
PUBLISH(CV_32FC2);
PUBLISH(CV_32FC3);
PUBLISH(CV_32FC4);
PUBLISH(CV_64F);
PUBLISH(CV_64FC1);
PUBLISH(CV_64FC2);
PUBLISH(CV_64FC3);
PUBLISH(CV_64FC4);
PUBLISH(CV_16F);
PUBLISH(CV_16FC1);
PUBLISH(CV_16FC2);
PUBLISH(CV_16FC3);
PUBLISH(CV_16FC4);
#undef PUBLISH_
#undef PUBLISH
return true;
}
#if defined(__GNUC__)
#pragma GCC visibility push(default)
#endif
#if defined(CV_PYTHON_3)
// === Python 3
static struct PyModuleDef cv2_moduledef =
{
PyModuleDef_HEAD_INIT,
MODULESTR,
"Python wrapper for OpenCV.",
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
special_methods
};
PyMODINIT_FUNC PyInit_cv2();
PyObject* PyInit_cv2()
{
import_array(); // from numpy
PyObject* m = PyModule_Create(&cv2_moduledef);
if (!init_body(m))
return NULL;
return m;
}
#else
// === Python 2
PyMODINIT_FUNC initcv2();
void initcv2()
{
import_array(); // from numpy
PyObject* m = Py_InitModule(MODULESTR, special_methods);
init_body(m);
}
#endif
+72
View File
@@ -0,0 +1,72 @@
#ifndef CV2_HPP
#define CV2_HPP
//warning number '5033' not a valid compiler warning in vc12
#if defined(_MSC_VER) && (_MSC_VER > 1800)
// eliminating duplicated round() declaration
#define HAVE_ROUND 1
#pragma warning(push)
#pragma warning(disable:5033) // 'register' is no longer a supported storage class
#endif
// #define CVPY_DYNAMIC_INIT
// #define Py_DEBUG
#if defined(CVPY_DYNAMIC_INIT) && !defined(Py_DEBUG)
# ifndef PYTHON3_LIMITED_API_VERSION
# define PYTHON3_LIMITED_API_VERSION 0x03060000
# endif
# define Py_LIMITED_API PYTHON3_LIMITED_API_VERSION
#endif
#include <cmath>
#include <Python.h>
#include <limits>
#if PY_MAJOR_VERSION < 3
#undef CVPY_DYNAMIC_INIT
#else
#define CV_PYTHON_3 1
#endif
#if defined(_MSC_VER) && (_MSC_VER > 1800)
#pragma warning(pop)
#endif
#define MODULESTR "cv2"
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/ndarrayobject.h>
#include "pycompat.hpp"
class ArgInfo
{
private:
static const uint32_t arg_outputarg_flag = 0x1;
static const uint32_t arg_arithm_op_src_flag = 0x2;
static const uint32_t arg_pathlike_flag = 0x4;
static const uint32_t arg_nd_mat_flag = 0x8;
public:
const char* name;
bool outputarg;
bool arithm_op_src;
bool pathlike;
bool nd_mat;
// more fields may be added if necessary
ArgInfo(const char* name_, uint32_t arg_) :
name(name_),
outputarg((arg_ & arg_outputarg_flag) != 0),
arithm_op_src((arg_ & arg_arithm_op_src_flag) != 0),
pathlike((arg_ & arg_pathlike_flag) != 0),
nd_mat((arg_ & arg_nd_mat_flag) != 0) {}
private:
ArgInfo(const ArgInfo&) = delete;
ArgInfo& operator=(const ArgInfo&) = delete;
};
#endif // CV2_HPP
File diff suppressed because it is too large Load Diff
+646
View File
@@ -0,0 +1,646 @@
#ifndef CV2_CONVERT_HPP
#define CV2_CONVERT_HPP
#include "cv2.hpp"
#include "cv2_util.hpp"
#include "cv2_numpy.hpp"
#include <vector>
#include <string>
#include <unordered_map>
#include <map>
#include <type_traits> // std::enable_if
extern PyTypeObject* pyopencv_Mat_TypePtr;
#define CV_HAS_CONVERSION_ERROR(x) (((x) == -1) && PyErr_Occurred())
inline bool isBool(PyObject* obj) CV_NOEXCEPT
{
return PyArray_IsScalar(obj, Bool) || PyBool_Check(obj);
}
//======================================================================================================================
// exception-safe pyopencv_to
template<typename _Tp> static
bool pyopencv_to_safe(PyObject* obj, _Tp& value, const ArgInfo& info)
{
try
{
return pyopencv_to(obj, value, info);
}
catch (const std::exception &e)
{
PyErr_SetString(opencv_error, cv::format("Conversion error: %s, what: %s", info.name, e.what()).c_str());
return false;
}
catch (...)
{
PyErr_SetString(opencv_error, cv::format("Conversion error: %s", info.name).c_str());
return false;
}
}
//======================================================================================================================
template<typename T, class TEnable = void> // TEnable is used for SFINAE checks
struct PyOpenCV_Converter
{
//static inline bool to(PyObject* obj, T& p, const ArgInfo& info);
//static inline PyObject* from(const T& src);
};
// --- Generic
template<typename T>
bool pyopencv_to(PyObject* obj, T& p, const ArgInfo& info) { return PyOpenCV_Converter<T>::to(obj, p, info); }
template<typename T>
PyObject* pyopencv_from(const T& src) { return PyOpenCV_Converter<T>::from(src); }
// --- Matx
template<typename _Tp, int m, int n>
bool pyopencv_to(PyObject* o, cv::Matx<_Tp, m, n>& mx, const ArgInfo& info)
{
if (!o || o == Py_None) {
return true;
}
cv::Mat tmp;
if (!pyopencv_to(o, tmp, info)) {
return false;
}
tmp.copyTo(mx);
return true;
}
template<typename _Tp, int m, int n>
PyObject* pyopencv_from(const cv::Matx<_Tp, m, n>& matx)
{
return pyopencv_from(cv::Mat(matx));
}
// --- bool
template<> bool pyopencv_to(PyObject* obj, bool& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const bool& value);
// --- Mat
template<> bool pyopencv_to(PyObject* o, cv::Mat& m, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Mat& m);
// --- Ptr
template<typename T>
struct PyOpenCV_Converter< cv::Ptr<T> >
{
static PyObject* from(const cv::Ptr<T>& p)
{
if (!p)
Py_RETURN_NONE;
return pyopencv_from(*p);
}
static bool to(PyObject *o, cv::Ptr<T>& p, const ArgInfo& info)
{
if (!o || o == Py_None)
return true;
p = cv::makePtr<T>();
return pyopencv_to(o, *p, info);
}
};
// --- ptr
template<> bool pyopencv_to(PyObject* obj, void*& ptr, const ArgInfo& info);
PyObject* pyopencv_from(void*& ptr);
// --- Scalar
template<> bool pyopencv_to(PyObject *o, cv::Scalar& s, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Scalar& src);
// --- size_t
template<> bool pyopencv_to(PyObject* obj, size_t& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const size_t& value);
// --- int
template<> bool pyopencv_to(PyObject* obj, int& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const int& value);
// --- int64
template<> bool pyopencv_to(PyObject* obj, int64& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const int64& value);
// There is conflict between "size_t" and "unsigned int".
// They are the same type on some 32-bit platforms.
template<typename T>
struct PyOpenCV_Converter
< T, typename std::enable_if< std::is_same<unsigned int, T>::value && !std::is_same<unsigned int, size_t>::value >::type >
{
static inline PyObject* from(const unsigned int& value)
{
return PyLong_FromUnsignedLong(value);
}
static inline bool to(PyObject* obj, unsigned int& value, const ArgInfo& info)
{
CV_UNUSED(info);
if(!obj || obj == Py_None)
return true;
if(PyInt_Check(obj))
value = (unsigned int)PyInt_AsLong(obj);
else if(PyLong_Check(obj))
value = (unsigned int)PyLong_AsLong(obj);
else
return false;
return value != (unsigned int)-1 || !PyErr_Occurred();
}
};
// There is conflict between "uint64_t" and "size_t".
// They are the same type on some 32-bit platforms.
template<typename T>
struct PyOpenCV_Converter
< T, typename std::enable_if< std::is_same<uint64_t, T>::value && !std::is_same<uint64_t, size_t>::value >::type >
{
static inline PyObject* from(const uint64_t& value)
{
return PyLong_FromUnsignedLongLong(value);
}
static inline bool to(PyObject* obj, uint64_t& value, const ArgInfo& info)
{
CV_UNUSED(info);
if(!obj || obj == Py_None)
return true;
if(PyInt_Check(obj))
value = (uint64_t)PyInt_AsUnsignedLongLongMask(obj);
else if(PyLong_Check(obj))
value = (uint64_t)PyLong_AsUnsignedLongLong(obj);
else
return false;
return value != (uint64_t)-1 || !PyErr_Occurred();
}
};
// There is conflict between "long long" and "int64".
// They are the same type on some 32-bit platforms.
template<typename T>
struct PyOpenCV_Converter
< T, typename std::enable_if< std::is_same<long long, T>::value && !std::is_same<long long, int64>::value >::type >
{
static inline PyObject* from(const long long& value)
{
return PyLong_FromLongLong(value);
}
static inline bool to(PyObject* obj, long long& value, const ArgInfo& info)
{
CV_UNUSED(info);
if(!obj || obj == Py_None)
return true;
else if(PyLong_Check(obj))
value = PyLong_AsLongLong(obj);
else
return false;
return value != (long long)-1 || !PyErr_Occurred();
}
};
// --- uchar
template<> bool pyopencv_to(PyObject* obj, uchar& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const uchar& value);
// --- char
template<> bool pyopencv_to(PyObject* obj, char& value, const ArgInfo& info);
// --- double
template<> bool pyopencv_to(PyObject* obj, double& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const double& value);
// --- float
template<> bool pyopencv_to(PyObject* obj, float& value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const float& value);
// --- string
template<> bool pyopencv_to(PyObject* obj, cv::String &value, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::String& value);
#if CV_VERSION_MAJOR == 3
template<> PyObject* pyopencv_from(const std::string& value);
#endif
// --- Size
template<> bool pyopencv_to(PyObject* obj, cv::Size& sz, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Size& sz);
template<> bool pyopencv_to(PyObject* obj, cv::Size_<float>& sz, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Size_<float>& sz);
// --- Rect
template<> bool pyopencv_to(PyObject* obj, cv::Rect& r, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Rect& r);
template<> bool pyopencv_to(PyObject* obj, cv::Rect2f& r, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Rect2f& r);
template<> bool pyopencv_to(PyObject* obj, cv::Rect2d& r, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Rect2d& r);
// --- RotatedRect
template<> bool pyopencv_to(PyObject* obj, cv::RotatedRect& dst, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::RotatedRect& src);
// --- Range
template<> bool pyopencv_to(PyObject* obj, cv::Range& r, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Range& r);
// --- Point
template<> bool pyopencv_to(PyObject* obj, cv::Point& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point& p);
template<> bool pyopencv_to(PyObject* obj, cv::Point2f& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point2f& p);
template<> bool pyopencv_to(PyObject* obj, cv::Point2d& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point2d& p);
template<> bool pyopencv_to(PyObject* obj, cv::Point3i& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point3i& p);
template<> bool pyopencv_to(PyObject* obj, cv::Point3f& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point3f& p);
template<> bool pyopencv_to(PyObject* obj, cv::Point3d& p, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::Point3d& p);
// --- Vec
template<typename _Tp, int cn>
bool pyopencv_to(PyObject* o, cv::Vec<_Tp, cn>& vec, const ArgInfo& info)
{
return pyopencv_to(o, (cv::Matx<_Tp, cn, 1>&)vec, info);
}
bool pyopencv_to(PyObject* obj, cv::Vec4d& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec4d& v);
bool pyopencv_to(PyObject* obj, cv::Vec4f& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec4f& v);
bool pyopencv_to(PyObject* obj, cv::Vec4i& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec4i& v);
bool pyopencv_to(PyObject* obj, cv::Vec3d& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec3d& v);
bool pyopencv_to(PyObject* obj, cv::Vec3f& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec3f& v);
bool pyopencv_to(PyObject* obj, cv::Vec3i& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec3i& v);
bool pyopencv_to(PyObject* obj, cv::Vec2d& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec2d& v);
bool pyopencv_to(PyObject* obj, cv::Vec2f& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec2f& v);
bool pyopencv_to(PyObject* obj, cv::Vec2i& v, ArgInfo& info);
PyObject* pyopencv_from(const cv::Vec2i& v);
// --- TermCriteria
template<> bool pyopencv_to(PyObject* obj, cv::TermCriteria& dst, const ArgInfo& info);
template<> PyObject* pyopencv_from(const cv::TermCriteria& src);
// --- Moments
template<> PyObject* pyopencv_from(const cv::Moments& m);
// --- pair
template<> PyObject* pyopencv_from(const std::pair<int, double>& src);
// --- vector
template <typename Tp>
struct pyopencvVecConverter;
template <typename Tp>
bool pyopencv_to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
return pyopencvVecConverter<Tp>::to(obj, value, info);
}
template <typename Tp>
PyObject* pyopencv_from(const std::vector<Tp>& value)
{
return pyopencvVecConverter<Tp>::from(value);
}
template<typename K, typename V>
bool pyopencv_to(PyObject *obj, std::map<K,V> &map, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
PyObject* py_key = nullptr;
PyObject* py_value = nullptr;
Py_ssize_t pos = 0;
if (!PyDict_Check(obj)) {
failmsg("Can't parse '%s'. Input argument isn't dict or"
" an instance of subtype of the dict type", info.name);
return false;
}
while(PyDict_Next(obj, &pos, &py_key, &py_value))
{
K cpp_key;
if (!pyopencv_to(py_key, cpp_key, ArgInfo("key", 0))) {
failmsg("Can't parse dict key. Key on position %lu has a wrong type", pos);
return false;
}
V cpp_value;
if (!pyopencv_to(py_value, cpp_value, ArgInfo("value", 0))) {
failmsg("Can't parse dict value. Value on position %lu has a wrong type", pos);
return false;
}
map.emplace(cpp_key, cpp_value);
}
return true;
}
template <typename Tp>
static bool pyopencv_to_generic_vec(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
if (info.nd_mat && PyArray_Check(obj))
{
/*
If obj is marked as nd mat and of array type, it is parsed to a single
mat in the target vector to avoid being split into multiple mats
*/
value.resize(1);
if (!pyopencv_to(obj, value.front(), info))
{
failmsg("Can't parse '%s'. Array item has a wrong type", info.name);
return false;
}
}
else // parse as sequence
{
if (!PySequence_Check(obj))
{
failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name);
return false;
}
const size_t n = static_cast<size_t>(PySequence_Size(obj));
value.resize(n);
for (size_t i = 0; i < n; i++)
{
SafeSeqItem item_wrap(obj, i);
if (!pyopencv_to(item_wrap.item, value[i], info))
{
failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i);
return false;
}
}
}
return true;
}
template<> inline bool pyopencv_to_generic_vec(PyObject* obj, std::vector<bool>& value, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
if (!PySequence_Check(obj))
{
failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name);
return false;
}
const size_t n = static_cast<size_t>(PySequence_Size(obj));
value.resize(n);
for (size_t i = 0; i < n; i++)
{
SafeSeqItem item_wrap(obj, i);
bool elem{};
if (!pyopencv_to(item_wrap.item, elem, info))
{
failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i);
return false;
}
value[i] = elem;
}
return true;
}
template <typename Tp>
static PyObject* pyopencv_from_generic_vec(const std::vector<Tp>& value)
{
Py_ssize_t n = static_cast<Py_ssize_t>(value.size());
PySafeObject seq(PyTuple_New(n));
for (Py_ssize_t i = 0; i < n; i++)
{
PyObject* item = pyopencv_from(value[i]);
// If item can't be assigned - PyTuple_SetItem raises exception and returns -1.
if (!item || PyTuple_SetItem(seq, i, item) == -1)
{
return NULL;
}
}
return seq.release();
}
template<> inline PyObject* pyopencv_from_generic_vec(const std::vector<bool>& value)
{
Py_ssize_t n = static_cast<Py_ssize_t>(value.size());
PySafeObject seq(PyTuple_New(n));
for (Py_ssize_t i = 0; i < n; i++)
{
bool elem = value[i];
PyObject* item = pyopencv_from(elem);
// If item can't be assigned - PyTuple_SetItem raises exception and returns -1.
if (!item || PyTuple_SetItem(seq, i, item) == -1)
{
return NULL;
}
}
return seq.release();
}
namespace traits {
template <bool Value>
struct BooleanConstant
{
static const bool value = Value;
typedef BooleanConstant<Value> type;
};
typedef BooleanConstant<true> TrueType;
typedef BooleanConstant<false> FalseType;
template <class T>
struct VoidType {
typedef void type;
};
template <class T, class DType = void>
struct IsRepresentableAsMatDataType : FalseType
{
};
template <class T>
struct IsRepresentableAsMatDataType<T, typename VoidType<typename cv::DataType<T>::channel_type>::type> : TrueType
{
};
// https://github.com/opencv/opencv/issues/20930
template <> struct IsRepresentableAsMatDataType<cv::RotatedRect, void> : FalseType {};
} // namespace traits
template <typename Tp>
struct pyopencvVecConverter
{
typedef typename std::vector<Tp>::iterator VecIt;
static bool to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
{
if (!PyArray_Check(obj))
{
return pyopencv_to_generic_vec(obj, value, info);
}
// If user passed an array it is possible to make faster conversions in several cases
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(obj);
const NPY_TYPES target_type = asNumpyType<Tp>();
const NPY_TYPES source_type = static_cast<NPY_TYPES>(PyArray_TYPE(array_obj));
if (target_type == NPY_OBJECT)
{
// Non-planar arrays representing objects (e.g. array of N Rect is an array of shape Nx4) have NPY_OBJECT
// as their target type.
return pyopencv_to_generic_vec(obj, value, info);
}
if (PyArray_NDIM(array_obj) > 1)
{
failmsg("Can't parse %dD array as '%s' vector argument", PyArray_NDIM(array_obj), info.name);
return false;
}
if (target_type != source_type)
{
// Source type requires conversion
// Allowed conversions for target type is handled in the corresponding pyopencv_to function
return pyopencv_to_generic_vec(obj, value, info);
}
// For all other cases, all array data can be directly copied to std::vector data
// Simple `memcpy` is not possible because NumPy array can reference a slice of the bigger array:
// ```
// arr = np.ones((8, 4, 5), dtype=np.int32)
// convertible_to_vector_of_int = arr[:, 0, 1]
// ```
value.resize(static_cast<size_t>(PyArray_SIZE(array_obj)));
const npy_intp item_step = PyArray_STRIDE(array_obj, 0) / PyArray_ITEMSIZE(array_obj);
const Tp* data_ptr = static_cast<Tp*>(PyArray_DATA(array_obj));
for (VecIt it = value.begin(); it != value.end(); ++it, data_ptr += item_step) {
*it = *data_ptr;
}
return true;
}
static PyObject* from(const std::vector<Tp>& value)
{
if (value.empty())
{
return PyTuple_New(0);
}
return from(value, ::traits::IsRepresentableAsMatDataType<Tp>());
}
private:
static PyObject* from(const std::vector<Tp>& value, ::traits::FalseType)
{
// Underlying type is not representable as Mat Data Type
return pyopencv_from_generic_vec(value);
}
static PyObject* from(const std::vector<Tp>& value, ::traits::TrueType)
{
// Underlying type is representable as Mat Data Type, so faster return type is available
typedef cv::DataType<Tp> DType;
typedef typename DType::channel_type UnderlyingArrayType;
// If Mat is always exposed as NumPy array this code path can be reduced to the following snipped:
// Mat src(value);
// PyObject* array = pyopencv_from(src);
// return PyArray_Squeeze(reinterpret_cast<PyArrayObject*>(array));
// This puts unnecessary restrictions on Mat object those might be avoided without losing the performance.
// Moreover, this version is a bit faster, because it doesn't create temporary objects with reference counting.
const NPY_TYPES target_type = asNumpyType<UnderlyingArrayType>();
const int cols = DType::channels;
PyObject* array = NULL;
if (cols == 1)
{
npy_intp dims = static_cast<npy_intp>(value.size());
array = PyArray_SimpleNew(1, &dims, target_type);
}
else
{
npy_intp dims[2] = {static_cast<npy_intp>(value.size()), cols};
array = PyArray_SimpleNew(2, dims, target_type);
}
if(!array)
{
// NumPy arrays with shape (N, 1) and (N) are not equal, so correct error message should distinguish
// them too.
cv::String shape;
if (cols > 1)
{
shape = cv::format("(%d x %d)", static_cast<int>(value.size()), cols);
}
else
{
shape = cv::format("(%d)", static_cast<int>(value.size()));
}
const cv::String error_message = cv::format("Can't allocate NumPy array for vector with dtype=%d and shape=%s",
static_cast<int>(target_type), shape.c_str());
emit_failmsg(PyExc_MemoryError, error_message.c_str());
return array;
}
// Fill the array
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(array);
UnderlyingArrayType* array_data = static_cast<UnderlyingArrayType*>(PyArray_DATA(array_obj));
// if Tp is representable as Mat DataType, so the following cast is pretty safe...
const UnderlyingArrayType* value_data = reinterpret_cast<const UnderlyingArrayType*>(value.data());
memcpy(array_data, value_data, sizeof(UnderlyingArrayType) * value.size() * static_cast<size_t>(cols));
return array;
}
};
// --- tuple
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
convert_to_python_tuple(const std::tuple<Tp...>&, PyObject*) { }
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
convert_to_python_tuple(const std::tuple<Tp...>& cpp_tuple, PyObject* py_tuple)
{
PyObject* item = pyopencv_from(std::get<I>(cpp_tuple));
if (!item)
return;
PyTuple_SetItem(py_tuple, I, item);
convert_to_python_tuple<I + 1, Tp...>(cpp_tuple, py_tuple);
}
template<typename... Ts>
PyObject* pyopencv_from(const std::tuple<Ts...>& cpp_tuple)
{
size_t size = sizeof...(Ts);
PyObject* py_tuple = PyTuple_New(size);
convert_to_python_tuple(cpp_tuple, py_tuple);
size_t actual_size = PyTuple_Size(py_tuple);
if (actual_size < size)
{
Py_DECREF(py_tuple);
return NULL;
}
return py_tuple;
}
#endif // CV2_CONVERT_HPP
+184
View File
@@ -0,0 +1,184 @@
#include "cv2_highgui.hpp"
#ifdef HAVE_OPENCV_HIGHGUI
#include "cv2_util.hpp"
#include "opencv2/highgui.hpp"
#include <map>
using namespace cv;
//======================================================================================================================
static void OnMouse(int event, int x, int y, int flags, void* param)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject *o = (PyObject*)param;
PyObject *args = Py_BuildValue("iiiiO", event, x, y, flags, PyTuple_GetItem(o, 1));
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
if (r == NULL)
PyErr_Print();
else
Py_DECREF(r);
Py_DECREF(args);
PyGILState_Release(gstate);
}
PyObject *pycvSetMouseCallback(PyObject*, PyObject *args, PyObject *kw)
{
const char *keywords[] = { "window_name", "on_mouse", "param", NULL };
char* name;
PyObject *on_mouse;
PyObject *param = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kw, "sO|O", (char**)keywords, &name, &on_mouse, &param))
return NULL;
if (!PyCallable_Check(on_mouse)) {
PyErr_SetString(PyExc_TypeError, "on_mouse must be callable");
return NULL;
}
if (param == NULL) {
param = Py_None;
}
PyObject* py_callback_info = Py_BuildValue("OO", on_mouse, param);
static std::map<std::string, PyObject*> registered_callbacks;
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
if (i != registered_callbacks.end())
{
Py_DECREF(i->second);
i->second = py_callback_info;
}
else
{
registered_callbacks.insert(std::pair<std::string, PyObject*>(std::string(name), py_callback_info));
}
ERRWRAP2(setMouseCallback(name, OnMouse, py_callback_info));
Py_RETURN_NONE;
}
//======================================================================================================================
static void OnChange(int pos, void *param)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject *o = (PyObject*)param;
PyObject *args = Py_BuildValue("(i)", pos);
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
if (r == NULL)
PyErr_Print();
else
Py_DECREF(r);
Py_DECREF(args);
PyGILState_Release(gstate);
}
// workaround for #20408, use nullptr, set value later
static int _createTrackbar(const String &trackbar_name, const String &window_name, int value, int count,
TrackbarCallback onChange, PyObject* py_callback_info)
{
int n = createTrackbar(trackbar_name, window_name, NULL, count, onChange, py_callback_info);
setTrackbarPos(trackbar_name, window_name, value);
return n;
}
PyObject *pycvCreateTrackbar(PyObject*, PyObject *args)
{
PyObject *on_change;
char* trackbar_name;
char* window_name;
int value;
int count;
if (!PyArg_ParseTuple(args, "ssiiO", &trackbar_name, &window_name, &value, &count, &on_change))
return NULL;
if (!PyCallable_Check(on_change)) {
PyErr_SetString(PyExc_TypeError, "on_change must be callable");
return NULL;
}
PyObject* py_callback_info = Py_BuildValue("OO", on_change, Py_None);
std::string name = std::string(window_name) + ":" + std::string(trackbar_name);
static std::map<std::string, PyObject*> registered_callbacks;
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
if (i != registered_callbacks.end())
{
Py_DECREF(i->second);
i->second = py_callback_info;
}
else
{
registered_callbacks.insert(std::pair<std::string, PyObject*>(name, py_callback_info));
}
ERRWRAP2(_createTrackbar(trackbar_name, window_name, value, count, OnChange, py_callback_info));
Py_RETURN_NONE;
}
//======================================================================================================================
static void OnButtonChange(int state, void *param)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject *o = (PyObject*)param;
PyObject *args;
if(PyTuple_GetItem(o, 1) != NULL)
{
args = Py_BuildValue("(iO)", state, PyTuple_GetItem(o,1));
}
else
{
args = Py_BuildValue("(i)", state);
}
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
if (r == NULL)
PyErr_Print();
else
Py_DECREF(r);
Py_DECREF(args);
PyGILState_Release(gstate);
}
PyObject *pycvCreateButton(PyObject*, PyObject *args, PyObject *kw)
{
const char* keywords[] = {"buttonName", "onChange", "userData", "buttonType", "initialButtonState", NULL};
PyObject *on_change;
PyObject *userdata = NULL;
char* button_name;
int button_type = 0;
int initial_button_state = 0;
if (!PyArg_ParseTupleAndKeywords(args, kw, "sO|Oii", (char**)keywords, &button_name, &on_change, &userdata, &button_type, &initial_button_state))
return NULL;
if (!PyCallable_Check(on_change)) {
PyErr_SetString(PyExc_TypeError, "onChange must be callable");
return NULL;
}
if (userdata == NULL) {
userdata = Py_None;
}
PyObject* py_callback_info = Py_BuildValue("OO", on_change, userdata);
std::string name(button_name);
static std::map<std::string, PyObject*> registered_callbacks;
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
if (i != registered_callbacks.end())
{
Py_DECREF(i->second);
i->second = py_callback_info;
}
else
{
registered_callbacks.insert(std::pair<std::string, PyObject*>(name, py_callback_info));
}
ERRWRAP2(createButton(button_name, OnButtonChange, py_callback_info, button_type, initial_button_state != 0));
Py_RETURN_NONE;
}
#endif // HAVE_OPENCV_HIGHGUI
+14
View File
@@ -0,0 +1,14 @@
#ifndef CV2_HIGHGUI_HPP
#define CV2_HIGHGUI_HPP
#include "cv2.hpp"
#include "opencv2/opencv_modules.hpp"
#ifdef HAVE_OPENCV_HIGHGUI
PyObject *pycvSetMouseCallback(PyObject*, PyObject *args, PyObject *kw);
// workaround for #20408, use nullptr, set value later
PyObject *pycvCreateTrackbar(PyObject*, PyObject *args);
PyObject *pycvCreateButton(PyObject*, PyObject *args, PyObject *kw);
#endif
#endif // CV2_HIGHGUI_HPP
+71
View File
@@ -0,0 +1,71 @@
// must be defined before importing numpy headers
// https://numpy.org/doc/1.17/reference/c-api.array.html#importing-the-api
#define NO_IMPORT_ARRAY
#define PY_ARRAY_UNIQUE_SYMBOL opencv_ARRAY_API
#include "cv2_numpy.hpp"
#include "cv2_util.hpp"
using namespace cv;
UMatData* NumpyAllocator::allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const
{
UMatData* u = new UMatData(this);
u->data = u->origdata = (uchar*)PyArray_DATA((PyArrayObject*) o);
npy_intp* _strides = PyArray_STRIDES((PyArrayObject*) o);
for( int i = 0; i < dims - 1; i++ )
step[i] = (size_t)_strides[i];
step[dims-1] = CV_ELEM_SIZE(type);
u->size = sizes[0]*step[0];
u->userdata = o;
return u;
}
UMatData* NumpyAllocator::allocate(int dims0, const int* sizes, int type, void* data, size_t* step, AccessFlag flags, UMatUsageFlags usageFlags) const
{
if( data != 0 )
{
// issue #6969: CV_Error(Error::StsAssert, "The data should normally be NULL!");
// probably this is safe to do in such extreme case
return stdAllocator->allocate(dims0, sizes, type, data, step, flags, usageFlags);
}
PyEnsureGIL gil;
int depth = CV_MAT_DEPTH(type);
int cn = CV_MAT_CN(type);
const int f = (int)(sizeof(size_t)/8);
int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE :
depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT :
depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT :
depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF : f*NPY_ULONGLONG + (f^1)*NPY_UINT;
int i, dims = dims0;
cv::AutoBuffer<npy_intp> _sizes(dims + 1);
for( i = 0; i < dims; i++ )
_sizes[i] = sizes[i];
if( cn > 1 )
_sizes[dims++] = cn;
PyObject* o = PyArray_SimpleNew(dims, _sizes.data(), typenum);
if(!o)
CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));
return allocate(o, dims0, sizes, type, step);
}
bool NumpyAllocator::allocate(UMatData* u, AccessFlag accessFlags, UMatUsageFlags usageFlags) const
{
return stdAllocator->allocate(u, accessFlags, usageFlags);
}
void NumpyAllocator::deallocate(UMatData* u) const
{
if(!u)
return;
PyEnsureGIL gil;
CV_Assert(u->urefcount >= 0);
CV_Assert(u->refcount >= 0);
if(u->refcount == 0)
{
PyObject* o = (PyObject*)u->userdata;
Py_XDECREF(o);
delete u;
}
}
+217
View File
@@ -0,0 +1,217 @@
#ifndef CV2_NUMPY_HPP
#define CV2_NUMPY_HPP
#include "cv2.hpp"
#include "opencv2/core.hpp"
class NumpyAllocator : public cv::MatAllocator
{
public:
NumpyAllocator() { stdAllocator = cv::Mat::getStdAllocator(); }
~NumpyAllocator() {}
cv::UMatData* allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const;
cv::UMatData* allocate(int dims0, const int* sizes, int type, void* data, size_t* step, cv::AccessFlag flags, cv::UMatUsageFlags usageFlags) const CV_OVERRIDE;
bool allocate(cv::UMatData* u, cv::AccessFlag accessFlags, cv::UMatUsageFlags usageFlags) const CV_OVERRIDE;
void deallocate(cv::UMatData* u) const CV_OVERRIDE;
const cv::MatAllocator* stdAllocator;
};
inline NumpyAllocator& GetNumpyAllocator() {static NumpyAllocator gNumpyAllocator;return gNumpyAllocator;}
//======================================================================================================================
// HACK(?): function from cv2_util.hpp
extern int failmsg(const char *fmt, ...);
namespace {
template<class T>
NPY_TYPES asNumpyType()
{
return NPY_OBJECT;
}
template<>
NPY_TYPES asNumpyType<bool>()
{
return NPY_BOOL;
}
#define CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(src, dst) \
template<> \
NPY_TYPES asNumpyType<src>() \
{ \
return NPY_##dst; \
} \
template<> \
NPY_TYPES asNumpyType<u##src>() \
{ \
return NPY_U##dst; \
}
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int8_t, INT8)
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int16_t, INT16)
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int32_t, INT32)
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int64_t, INT64)
#undef CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION
template<>
NPY_TYPES asNumpyType<float>()
{
return NPY_FLOAT;
}
template<>
NPY_TYPES asNumpyType<double>()
{
return NPY_DOUBLE;
}
template <class T>
PyArray_Descr* getNumpyTypeDescriptor()
{
return PyArray_DescrFromType(asNumpyType<T>());
}
template <>
PyArray_Descr* getNumpyTypeDescriptor<size_t>()
{
#if SIZE_MAX == ULONG_MAX
return PyArray_DescrFromType(NPY_ULONG);
#elif SIZE_MAX == ULLONG_MAX
return PyArray_DescrFromType(NPY_ULONGLONG);
#else
return PyArray_DescrFromType(NPY_UINT);
#endif
}
template <class T, class U>
bool isRepresentable(U value) {
return (std::numeric_limits<T>::min() <= value) && (value <= std::numeric_limits<T>::max());
}
template<class T>
bool canBeSafelyCasted(PyObject* obj, PyArray_Descr* to)
{
return PyArray_CanCastTo(PyArray_DescrFromScalar(obj), to) != 0;
}
template<>
bool canBeSafelyCasted<size_t>(PyObject* obj, PyArray_Descr* to)
{
PyArray_Descr* from = PyArray_DescrFromScalar(obj);
if (PyArray_CanCastTo(from, to))
{
return true;
}
else
{
// False negative scenarios:
// - Signed input is positive so it can be safely cast to unsigned output
// - Input has wider limits but value is representable within output limits
// - All the above
if (PyDataType_ISSIGNED(from))
{
int64_t input = 0;
PyArray_CastScalarToCtype(obj, &input, getNumpyTypeDescriptor<int64_t>());
return (input >= 0) && isRepresentable<size_t>(static_cast<uint64_t>(input));
}
else
{
uint64_t input = 0;
PyArray_CastScalarToCtype(obj, &input, getNumpyTypeDescriptor<uint64_t>());
return isRepresentable<size_t>(input);
}
return false;
}
}
template<class T>
bool parseNumpyScalar(PyObject* obj, T& value)
{
if (PyArray_CheckScalar(obj))
{
// According to the numpy documentation:
// There are 21 statically-defined PyArray_Descr objects for the built-in data-types
// So descriptor pointer is not owning.
PyArray_Descr* to = getNumpyTypeDescriptor<T>();
if (canBeSafelyCasted<T>(obj, to))
{
PyArray_CastScalarToCtype(obj, &value, to);
return true;
}
}
return false;
}
struct SafeSeqItem
{
PyObject * item;
SafeSeqItem(PyObject *obj, size_t idx) { item = PySequence_GetItem(obj, idx); }
~SafeSeqItem() { Py_XDECREF(item); }
private:
SafeSeqItem(const SafeSeqItem&); // = delete
SafeSeqItem& operator=(const SafeSeqItem&); // = delete
};
template <class T>
class RefWrapper
{
public:
RefWrapper(T& item) : item_(item) {}
T& get() CV_NOEXCEPT { return item_; }
private:
T& item_;
};
// In order to support this conversion on 3.x branch - use custom reference_wrapper
// and C-style array instead of std::array<T, N>
template <class T, std::size_t N>
bool parseSequence(PyObject* obj, RefWrapper<T> (&value)[N], const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
if (!PySequence_Check(obj))
{
failmsg("Can't parse '%s'. Input argument doesn't provide sequence "
"protocol", info.name);
return false;
}
const std::size_t sequenceSize = PySequence_Size(obj);
if (sequenceSize != N)
{
failmsg("Can't parse '%s'. Expected sequence length %lu, got %lu",
info.name, N, sequenceSize);
return false;
}
for (std::size_t i = 0; i < N; ++i)
{
SafeSeqItem seqItem(obj, i);
if (!pyopencv_to(seqItem.item, value[i].get(), info))
{
failmsg("Can't parse '%s'. Sequence item with index %lu has a "
"wrong type", info.name, i);
return false;
}
}
return true;
}
} // namespace
#endif // CV2_NUMPY_HPP
+186
View File
@@ -0,0 +1,186 @@
#include "cv2_util.hpp"
#include "opencv2/core.hpp"
#include "opencv2/core/utils/configuration.private.hpp"
#include "opencv2/core/utils/logger.hpp"
PyObject* opencv_error = NULL;
cv::TLSData<std::vector<std::string> > conversionErrorsTLS;
using namespace cv;
//======================================================================================================================
bool isPythonBindingsDebugEnabled()
{
static bool param_debug = cv::utils::getConfigurationParameterBool("OPENCV_PYTHON_DEBUG", false);
return param_debug;
}
void emit_failmsg(PyObject * exc, const char *msg)
{
static bool param_debug = isPythonBindingsDebugEnabled();
if (param_debug)
{
CV_LOG_WARNING(NULL, "Bindings conversion failed: " << msg);
}
PyErr_SetString(exc, msg);
}
int failmsg(const char *fmt, ...)
{
char str[1000];
va_list ap;
va_start(ap, fmt);
vsnprintf(str, sizeof(str), fmt, ap);
va_end(ap);
emit_failmsg(PyExc_TypeError, str);
return 0;
}
PyObject* failmsgp(const char *fmt, ...)
{
char str[1000];
va_list ap;
va_start(ap, fmt);
vsnprintf(str, sizeof(str), fmt, ap);
va_end(ap);
emit_failmsg(PyExc_TypeError, str);
return 0;
}
void pyRaiseCVException(const cv::Exception &e)
{
PyObject* temp_obj = PyString_FromString(e.file.c_str());
PyObject_SetAttrString(opencv_error, "file", temp_obj);
Py_DECREF(temp_obj);
temp_obj = PyString_FromString(e.func.c_str());
PyObject_SetAttrString(opencv_error, "func", temp_obj);
Py_DECREF(temp_obj);
temp_obj = PyInt_FromLong(e.line);
PyObject_SetAttrString(opencv_error, "line", temp_obj);
Py_DECREF(temp_obj);
temp_obj = PyInt_FromLong(e.code);
PyObject_SetAttrString(opencv_error, "code", temp_obj);
Py_DECREF(temp_obj);
temp_obj = PyString_FromString(e.msg.c_str());
PyObject_SetAttrString(opencv_error, "msg", temp_obj);
Py_DECREF(temp_obj);
temp_obj = PyString_FromString(e.err.c_str());
PyObject_SetAttrString(opencv_error, "err", temp_obj);
Py_DECREF(temp_obj);
PyErr_SetString(opencv_error, e.what());
}
//======================================================================================================================
void pyRaiseCVOverloadException(const std::string& functionName)
{
const std::vector<std::string>& conversionErrors = conversionErrorsTLS.getRef();
const std::size_t conversionErrorsCount = conversionErrors.size();
if (conversionErrorsCount > 0)
{
// In modern std libraries small string optimization is used = no dynamic memory allocations,
// but it can be applied only for string with length < 18 symbols (in GCC)
const std::string bullet = "\n - ";
// Estimate required buffer size - save dynamic memory allocations = faster
std::size_t requiredBufferSize = bullet.size() * conversionErrorsCount;
for (std::size_t i = 0; i < conversionErrorsCount; ++i)
{
requiredBufferSize += conversionErrors[i].size();
}
// Only string concatenation is required so std::string is way faster than
// std::ostringstream
std::string errorMessage("Overload resolution failed:");
errorMessage.reserve(errorMessage.size() + requiredBufferSize);
for (std::size_t i = 0; i < conversionErrorsCount; ++i)
{
errorMessage += bullet;
errorMessage += conversionErrors[i];
}
cv::Exception exception(Error::StsBadArg, errorMessage, functionName, "", -1);
pyRaiseCVException(exception);
}
else
{
cv::Exception exception(Error::StsInternal, "Overload resolution failed, but no errors reported",
functionName, "", -1);
pyRaiseCVException(exception);
}
}
void pyPopulateArgumentConversionErrors()
{
if (PyErr_Occurred())
{
PySafeObject exception_type;
PySafeObject exception_value;
PySafeObject exception_traceback;
PyErr_Fetch(exception_type, exception_value, exception_traceback);
PyErr_NormalizeException(exception_type, exception_value,
exception_traceback);
PySafeObject exception_message(PyObject_Str(exception_value));
std::string message;
getUnicodeString(exception_message, message);
conversionErrorsTLS.getRef().push_back(std::move(message));
}
}
//======================================================================================================================
static int OnError(int status, const char *func_name, const char *err_msg, const char *file_name, int line, void *userdata)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject *on_error = (PyObject*)userdata;
PyObject *args = Py_BuildValue("isssi", status, func_name, err_msg, file_name, line);
PyObject *r = PyObject_Call(on_error, args, NULL);
if (r == NULL) {
PyErr_Print();
} else {
Py_DECREF(r);
}
Py_DECREF(args);
PyGILState_Release(gstate);
return 0; // The return value isn't used
}
PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw)
{
const char *keywords[] = { "on_error", NULL };
PyObject *on_error;
if (!PyArg_ParseTupleAndKeywords(args, kw, "O", (char**)keywords, &on_error))
return NULL;
if ((on_error != Py_None) && !PyCallable_Check(on_error)) {
PyErr_SetString(PyExc_TypeError, "on_error must be callable");
return NULL;
}
// Keep track of the previous handler parameter, so we can decref it when no longer used
static PyObject* last_on_error = NULL;
if (last_on_error) {
Py_DECREF(last_on_error);
last_on_error = NULL;
}
if (on_error == Py_None) {
ERRWRAP2(redirectError(NULL));
} else {
last_on_error = on_error;
Py_INCREF(last_on_error);
ERRWRAP2(redirectError(OnError, last_on_error));
}
Py_RETURN_NONE;
}
+138
View File
@@ -0,0 +1,138 @@
#ifndef CV2_UTIL_HPP
#define CV2_UTIL_HPP
#include "cv2.hpp"
#include "opencv2/core.hpp"
#include "opencv2/core/utils/tls.hpp"
#include <vector>
#include <string>
//======================================================================================================================
bool isPythonBindingsDebugEnabled();
void emit_failmsg(PyObject * exc, const char *msg);
int failmsg(const char *fmt, ...);
PyObject* failmsgp(const char *fmt, ...);
//======================================================================================================================
class PyAllowThreads
{
public:
PyAllowThreads() : _state(PyEval_SaveThread()) {}
~PyAllowThreads()
{
PyEval_RestoreThread(_state);
}
private:
PyThreadState* _state;
};
class PyEnsureGIL
{
public:
PyEnsureGIL() : _state(PyGILState_Ensure()) {}
~PyEnsureGIL()
{
PyGILState_Release(_state);
}
private:
PyGILState_STATE _state;
};
/**
* Light weight RAII wrapper for `PyObject*` owning references.
* In comparison to C++11 `std::unique_ptr` with custom deleter, it provides
* implicit conversion functions that might be useful to initialize it with
* Python functions those returns owning references through the `PyObject**`
* e.g. `PyErr_Fetch` or directly pass it to functions those want to borrow
* reference to object (doesn't extend object lifetime) e.g. `PyObject_Str`.
*/
class PySafeObject
{
public:
PySafeObject() : obj_(NULL) {}
explicit PySafeObject(PyObject* obj) : obj_(obj) {}
~PySafeObject()
{
Py_CLEAR(obj_);
}
operator PyObject*()
{
return obj_;
}
operator PyObject**()
{
return &obj_;
}
operator bool() {
return obj_ != nullptr;
}
PyObject* release()
{
PyObject* obj = obj_;
obj_ = NULL;
return obj;
}
private:
PyObject* obj_;
// Explicitly disable copy operations
PySafeObject(const PySafeObject*); // = delete
PySafeObject& operator=(const PySafeObject&); // = delete
};
//======================================================================================================================
extern PyObject* opencv_error;
void pyRaiseCVException(const cv::Exception &e);
#define ERRWRAP2(expr) \
try \
{ \
PyAllowThreads allowThreads; \
expr; \
} \
catch (const cv::Exception &e) \
{ \
pyRaiseCVException(e); \
return 0; \
} \
catch (const std::exception &e) \
{ \
PyErr_SetString(opencv_error, e.what()); \
return 0; \
} \
catch (...) \
{ \
PyErr_SetString(opencv_error, "Unknown C++ exception from OpenCV code"); \
return 0; \
}
//======================================================================================================================
extern cv::TLSData<std::vector<std::string> > conversionErrorsTLS;
inline void pyPrepareArgumentConversionErrorsStorage(std::size_t size)
{
std::vector<std::string>& conversionErrors = conversionErrorsTLS.getRef();
conversionErrors.clear();
conversionErrors.reserve(size);
}
void pyRaiseCVOverloadException(const std::string& functionName);
void pyPopulateArgumentConversionErrors();
//======================================================================================================================
PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw);
#endif // CV2_UTIL_HPP
+1556
View File
File diff suppressed because it is too large Load Diff
+1253
View File
File diff suppressed because it is too large Load Diff
+371
View File
@@ -0,0 +1,371 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's 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.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// 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 Intel Corporation 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.
//
//M*/
// Defines for Python 2/3 compatibility.
#ifndef __PYCOMPAT_HPP__
#define __PYCOMPAT_HPP__
#include <string>
#if PY_MAJOR_VERSION >= 3
// Python3 treats all ints as longs, PyInt_X functions have been removed.
#define PyInt_Check PyLong_Check
#define PyInt_CheckExact PyLong_CheckExact
#define PyInt_AsLong PyLong_AsLong
#define PyInt_AS_LONG PyLong_AS_LONG
#define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask
#define PyInt_FromLong PyLong_FromLong
#define PyNumber_Int PyNumber_Long
#define PyString_FromString PyUnicode_FromString
#define PyString_FromStringAndSize PyUnicode_FromStringAndSize
#endif // PY_MAJOR >=3
#ifndef PyType_CheckExact
#define PyType_CheckExact(obj) (Py_TYPE(op) == &PyType_Type)
#endif // !PyType_CheckExact
static inline bool getUnicodeString(PyObject * obj, std::string &str)
{
bool res = false;
if (PyUnicode_Check(obj))
{
PyObject * bytes = PyUnicode_AsUTF8String(obj);
if (PyBytes_Check(bytes))
{
const char * raw = PyBytes_AsString(bytes);
if (raw)
{
str = std::string(raw);
res = true;
}
}
Py_XDECREF(bytes);
}
else if (PyBytes_Check(obj))
{
const char * raw = PyBytes_AsString(obj);
if (raw)
{
str = std::string(raw);
res = true;
}
}
#if PY_MAJOR_VERSION < 3
else if (PyString_Check(obj))
{
const char * raw = PyString_AsString(obj);
if (raw)
{
str = std::string(raw);
res = true;
}
}
#endif
return res;
}
static inline
std::string getPyObjectAttr(PyObject* obj, const char* attrName)
{
std::string obj_name;
PyObject* cls_name_obj = PyObject_GetAttrString(obj, attrName);
if (cls_name_obj && !getUnicodeString(cls_name_obj, obj_name)) {
obj_name.clear();
}
#ifndef Py_LIMITED_API
if (PyType_CheckExact(obj) && obj_name.empty())
{
obj_name = reinterpret_cast<PyTypeObject*>(obj)->tp_name;
}
#endif
if (obj_name.empty()) {
obj_name = "<UNAVAILABLE>";
}
return obj_name;
}
static inline
std::string getPyObjectNameAttr(PyObject* obj)
{
return getPyObjectAttr(obj, "__name__");
}
//==================================================================================================
#define CV_PY_FN_WITH_KW_(fn, flags) (PyCFunction)(void*)(PyCFunctionWithKeywords)(fn), (flags) | METH_VARARGS | METH_KEYWORDS
#define CV_PY_FN_NOARGS_(fn, flags) (PyCFunction)(fn), (flags) | METH_NOARGS
#define CV_PY_FN_WITH_KW(fn) CV_PY_FN_WITH_KW_(fn, 0)
#define CV_PY_FN_NOARGS(fn) CV_PY_FN_NOARGS_(fn, 0)
#define CV_PY_TO_CLASS(TYPE) \
template<> \
bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) \
{ \
if (!dst || dst == Py_None) \
return true; \
Ptr<TYPE> ptr; \
\
if (!pyopencv_to(dst, ptr, info)) return false; \
src = *ptr; \
return true; \
}
#define CV_PY_FROM_CLASS(TYPE) \
template<> \
PyObject* pyopencv_from(const TYPE& src) \
{ \
Ptr<TYPE> ptr(new TYPE()); \
\
*ptr = src; \
return pyopencv_from(ptr); \
}
#define CV_PY_TO_CLASS_PTR(TYPE) \
template<> \
bool pyopencv_to(PyObject* dst, TYPE*& src, const ArgInfo& info) \
{ \
if (!dst || dst == Py_None) \
return true; \
Ptr<TYPE> ptr; \
\
if (!pyopencv_to(dst, ptr, info)) return false; \
src = ptr; \
return true; \
}
#define CV_PY_FROM_CLASS_PTR(TYPE) \
static PyObject* pyopencv_from(TYPE*& src) \
{ \
return pyopencv_from(Ptr<TYPE>(src)); \
}
#define CV_PY_TO_ENUM(TYPE) \
template<> \
bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) \
{ \
if (!dst || dst == Py_None) \
return true; \
int underlying = 0; \
\
if (!pyopencv_to(dst, underlying, info)) return false; \
src = static_cast<TYPE>(underlying); \
return true; \
}
#define CV_PY_FROM_ENUM(TYPE) \
template<> \
PyObject* pyopencv_from(const TYPE& src) \
{ \
return pyopencv_from(static_cast<int>(src)); \
}
//==================================================================================================
#if PY_MAJOR_VERSION >= 3
#define CVPY_TYPE_HEAD PyVarObject_HEAD_INIT(&PyType_Type, 0)
#define CVPY_TYPE_INCREF(T) Py_INCREF(T)
#else
#define CVPY_TYPE_HEAD PyObject_HEAD_INIT(&PyType_Type) 0,
#define CVPY_TYPE_INCREF(T) _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA (T)->ob_refcnt++
#endif
#define CVPY_TYPE_DECLARE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \
struct pyopencv_##CLASS_ID##_t \
{ \
PyObject_HEAD \
STORAGE v; \
}; \
static PyTypeObject pyopencv_##CLASS_ID##_TypeXXX = \
{ \
CVPY_TYPE_HEAD \
MODULESTR SCOPE"."#EXPORT_NAME, \
sizeof(pyopencv_##CLASS_ID##_t), \
}; \
static PyTypeObject * pyopencv_##CLASS_ID##_TypePtr = &pyopencv_##CLASS_ID##_TypeXXX; \
static bool pyopencv_##CLASS_ID##_getp(PyObject * self, STORAGE * & dst) \
{ \
if (PyObject_TypeCheck(self, pyopencv_##CLASS_ID##_TypePtr)) \
{ \
dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \
return true; \
} \
return false; \
} \
static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \
{ \
pyopencv_##CLASS_ID##_t *m = PyObject_NEW(pyopencv_##CLASS_ID##_t, pyopencv_##CLASS_ID##_TypePtr); \
new (&(m->v)) STORAGE(r); \
return (PyObject*)m; \
} \
static void pyopencv_##CLASS_ID##_dealloc(PyObject* self) \
{ \
((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \
PyObject_Del(self); \
} \
static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \
{ \
char str[1000]; \
snprintf(str, sizeof(str), "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \
return PyString_FromString(str); \
}
#define CVPY_TYPE_INIT_STATIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \
{ \
pyopencv_##CLASS_ID##_TypePtr->tp_base = pyopencv_##BASE##_TypePtr; \
pyopencv_##CLASS_ID##_TypePtr->tp_dealloc = pyopencv_##CLASS_ID##_dealloc; \
pyopencv_##CLASS_ID##_TypePtr->tp_repr = pyopencv_##CLASS_ID##_repr; \
pyopencv_##CLASS_ID##_TypePtr->tp_getset = pyopencv_##CLASS_ID##_getseters; \
pyopencv_##CLASS_ID##_TypePtr->tp_init = (initproc) CONSTRUCTOR; \
pyopencv_##CLASS_ID##_TypePtr->tp_methods = pyopencv_##CLASS_ID##_methods; \
pyopencv_##CLASS_ID##_TypePtr->tp_alloc = PyType_GenericAlloc; \
pyopencv_##CLASS_ID##_TypePtr->tp_new = PyType_GenericNew; \
pyopencv_##CLASS_ID##_TypePtr->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; \
if (PyType_Ready(pyopencv_##CLASS_ID##_TypePtr) != 0) \
{ \
ERROR_HANDLER; \
} \
CVPY_TYPE_INCREF(pyopencv_##CLASS_ID##_TypePtr); \
if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \
{ \
printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \
ERROR_HANDLER; \
} \
}
//==================================================================================================
#define CVPY_TYPE_DECLARE_DYNAMIC(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \
struct pyopencv_##CLASS_ID##_t \
{ \
PyObject_HEAD \
STORAGE v; \
}; \
static PyObject * pyopencv_##CLASS_ID##_TypePtr = 0; \
static bool pyopencv_##CLASS_ID##_getp(PyObject * self, STORAGE * & dst) \
{ \
if (PyObject_TypeCheck(self, (PyTypeObject*)pyopencv_##CLASS_ID##_TypePtr)) \
{ \
dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \
return true; \
} \
return false; \
} \
static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \
{ \
pyopencv_##CLASS_ID##_t *m = PyObject_New(pyopencv_##CLASS_ID##_t, (PyTypeObject*)pyopencv_##CLASS_ID##_TypePtr); \
new (&(m->v)) STORAGE(r); \
return (PyObject*)m; \
} \
static void pyopencv_##CLASS_ID##_dealloc(PyObject* self) \
{ \
((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \
PyObject_Del(self); \
} \
static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \
{ \
char str[1000]; \
snprintf(str, sizeof(str), "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \
return PyString_FromString(str); \
} \
static PyType_Slot pyopencv_##CLASS_ID##_Slots[] = \
{ \
{Py_tp_dealloc, 0}, \
{Py_tp_repr, 0}, \
{Py_tp_getset, 0}, \
{Py_tp_init, 0}, \
{Py_tp_methods, 0}, \
{Py_tp_alloc, 0}, \
{Py_tp_new, 0}, \
{0, 0} \
}; \
static PyType_Spec pyopencv_##CLASS_ID##_Spec = \
{ \
MODULESTR SCOPE"."#EXPORT_NAME, \
sizeof(pyopencv_##CLASS_ID##_t), \
0, \
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, \
pyopencv_##CLASS_ID##_Slots \
};
#define CVPY_TYPE_INIT_DYNAMIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \
{ \
pyopencv_##CLASS_ID##_Slots[0].pfunc /*tp_dealloc*/ = (void*)pyopencv_##CLASS_ID##_dealloc; \
pyopencv_##CLASS_ID##_Slots[1].pfunc /*tp_repr*/ = (void*)pyopencv_##CLASS_ID##_repr; \
pyopencv_##CLASS_ID##_Slots[2].pfunc /*tp_getset*/ = (void*)pyopencv_##CLASS_ID##_getseters; \
pyopencv_##CLASS_ID##_Slots[3].pfunc /*tp_init*/ = (void*) CONSTRUCTOR; \
pyopencv_##CLASS_ID##_Slots[4].pfunc /*tp_methods*/ = pyopencv_##CLASS_ID##_methods; \
pyopencv_##CLASS_ID##_Slots[5].pfunc /*tp_alloc*/ = (void*)PyType_GenericAlloc; \
pyopencv_##CLASS_ID##_Slots[6].pfunc /*tp_new*/ = (void*)PyType_GenericNew; \
PyObject * bases = 0; \
if (pyopencv_##BASE##_TypePtr) \
bases = PyTuple_Pack(1, pyopencv_##BASE##_TypePtr); \
pyopencv_##CLASS_ID##_TypePtr = PyType_FromSpecWithBases(&pyopencv_##CLASS_ID##_Spec, bases); \
if (!pyopencv_##CLASS_ID##_TypePtr) \
{ \
printf("Failed to create type from spec: " #CLASS_ID ", base (" #BASE ")\n"); \
ERROR_HANDLER; \
} \
if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \
{ \
printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \
Py_DECREF(pyopencv_##CLASS_ID##_TypePtr); \
ERROR_HANDLER; \
} \
Py_DECREF(pyopencv_##CLASS_ID##_TypePtr); \
}
// Debug module load:
//
// else \
// { \
// printf("Init: " #NAME ", base (" #BASE ") -> %p" "\n", pyopencv_##NAME##_TypePtr); \
// } \
#endif // END HEADER GUARD
@@ -0,0 +1,35 @@
from .nodes import (
NamespaceNode,
ClassNode,
ClassProperty,
EnumerationNode,
FunctionNode,
ConstantNode,
TypeNode,
OptionalTypeNode,
TupleTypeNode,
AliasTypeNode,
SequenceTypeNode,
AnyTypeNode,
AggregatedTypeNode,
PathLikeTypeNode,
)
from .types_conversion import (
replace_template_parameters_with_placeholders,
get_template_instantiation_type,
create_type_node
)
from .ast_utils import (
SymbolName,
ScopeNotFoundError,
SymbolNotFoundError,
find_scope,
find_class_node,
create_class_node,
create_function_node,
resolve_enum_scopes
)
from .generation import generate_typing_stubs
@@ -0,0 +1,474 @@
__all__ = [
"apply_manual_api_refinement"
]
from typing import cast, Sequence, Callable, Iterable, Optional
from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode,
ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode,
AggregatedTypeNode, CallableTypeNode, AnyTypeNode,
TupleTypeNode, UnionTypeNode, ProtocolClassNode,
DictTypeNode, ClassTypeNode, AliasRefTypeNode)
from .ast_utils import (find_function_node, SymbolName,
for_each_function_overload)
from .types_conversion import create_type_node
def apply_manual_api_refinement(root: NamespaceNode) -> None:
refine_highgui_module(root)
refine_cuda_module(root)
export_matrix_type_constants(root)
refine_dnn_module(root)
# Export OpenCV exception class
builtin_exception = root.add_class("Exception")
builtin_exception.is_exported = False
root.add_class("error", (builtin_exception, ), ERROR_CLASS_PROPERTIES)
for symbol_name, refine_symbol in NODES_TO_REFINE.items():
refine_symbol(root, symbol_name)
version_constant = root.add_constant("__version__", "<unused>")
version_constant._value_type = "str"
convert_returned_scalar_to_tuple(root)
"""
def redirectError(
onError: Callable[[int, str, str, str, int], None] | None
) -> None: ...
"""
root.add_function("redirectError", [
FunctionNode.Arg(
"onError",
OptionalTypeNode(
CallableTypeNode(
"ErrorCallback",
[
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.str_(),
PrimitiveTypeNode.str_(),
PrimitiveTypeNode.str_(),
PrimitiveTypeNode.int_()
]
)
)
)
])
def make_optional_none_return(root_node: NamespaceNode,
function_symbol_name: SymbolName) -> None:
"""
Make return type Optional[MatLike],
for the functions that may return None.
"""
function = find_function_node(root_node, function_symbol_name)
for overload in function.overloads:
if overload.return_type is not None:
if not isinstance(overload.return_type.type_node, OptionalTypeNode):
overload.return_type.type_node = OptionalTypeNode(
overload.return_type.type_node
)
def export_matrix_type_constants(root: NamespaceNode) -> None:
MAX_PREDEFINED_CHANNELS = 4
depth_names = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32S",
"CV_32F", "CV_64F", "CV_16F")
for depth_value, depth_name in enumerate(depth_names):
# Export depth constants
root.add_constant(depth_name, str(depth_value))
# Export predefined types
for c in range(MAX_PREDEFINED_CHANNELS):
root.add_constant(f"{depth_name}C{c + 1}",
f"{depth_value + 8 * c}")
# Export type creation function
root.add_function(
f"{depth_name}C",
(FunctionNode.Arg("channels", PrimitiveTypeNode.int_()), ),
FunctionNode.RetType(PrimitiveTypeNode.int_())
)
# Export CV_MAKETYPE
root.add_function(
"CV_MAKETYPE",
(FunctionNode.Arg("depth", PrimitiveTypeNode.int_()),
FunctionNode.Arg("channels", PrimitiveTypeNode.int_())),
FunctionNode.RetType(PrimitiveTypeNode.int_())
)
def make_optional_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]:
def _make_optional_arg(root_node: NamespaceNode,
function_symbol_name: SymbolName) -> None:
function = find_function_node(root_node, function_symbol_name)
for arg_name in arg_names:
found_overload_with_arg = False
for overload in function.overloads:
arg_idx = _find_argument_index(overload.arguments, arg_name)
# skip overloads without this argument
if arg_idx is None:
continue
# Avoid multiplying optional qualification
if isinstance(overload.arguments[arg_idx].type_node, OptionalTypeNode):
continue
overload.arguments[arg_idx].type_node = OptionalTypeNode(
cast(TypeNode, overload.arguments[arg_idx].type_node)
)
found_overload_with_arg = True
if not found_overload_with_arg:
raise RuntimeError(
f"Failed to find argument with name: '{arg_name}'"
f" in '{function_symbol_name.name}' overloads"
)
return _make_optional_arg
def convert_returned_scalar_to_tuple(root: NamespaceNode) -> None:
"""Force `tuple[float, float, float, float]` usage instead of Scalar alias
for return types due to `pyopencv_from` specialization for Scalar type.
"""
float_4_tuple_node = TupleTypeNode(
"ScalarOutput",
items=(PrimitiveTypeNode.float_(),) * 4
)
def fix_scalar_return_type(fn: FunctionNode.Overload):
if fn.return_type is None:
return
if fn.return_type.type_node.typename == "Scalar":
fn.return_type.type_node = float_4_tuple_node
for overload in for_each_function_overload(root):
fix_scalar_return_type(overload)
for ns in root.namespaces.values():
for overload in for_each_function_overload(ns):
fix_scalar_return_type(overload)
def refine_cuda_module(root: NamespaceNode) -> None:
def fix_cudaoptflow_enums_names() -> None:
for class_name in ("NvidiaOpticalFlow_1_0", "NvidiaOpticalFlow_2_0"):
if class_name not in cuda_root.classes:
continue
opt_flow_class = cuda_root.classes[class_name]
_trim_class_name_from_argument_types(
for_each_function_overload(opt_flow_class), class_name
)
def fix_namespace_usage_scope(cuda_ns: NamespaceNode) -> None:
USED_TYPES = ("GpuMat", "Stream")
def fix_type_usage(type_node: TypeNode) -> None:
if isinstance(type_node, AggregatedTypeNode):
for item in type_node.items:
fix_type_usage(item)
if isinstance(type_node, ASTNodeTypeNode):
if type_node._typename in USED_TYPES:
type_node._typename = f"cuda_{type_node._typename}"
for overload in for_each_function_overload(cuda_ns):
if overload.return_type is not None:
fix_type_usage(overload.return_type.type_node)
for type_node in [arg.type_node for arg in overload.arguments
if arg.type_node is not None]:
fix_type_usage(type_node)
if "cuda" not in root.namespaces:
return
cuda_root = root.namespaces["cuda"]
fix_cudaoptflow_enums_names()
for ns in [ns for ns_name, ns in root.namespaces.items()
if ns_name.startswith("cuda")]:
fix_namespace_usage_scope(ns)
def refine_highgui_module(root: NamespaceNode) -> None:
# Check if library is built with enabled highgui module
if "destroyAllWindows" not in root.functions:
return
"""
def createTrackbar(trackbarName: str,
windowName: str,
value: int,
count: int,
onChange: Callable[[int], None]) -> None: ...
"""
root.add_function(
"createTrackbar",
[
FunctionNode.Arg("trackbarName", PrimitiveTypeNode.str_()),
FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()),
FunctionNode.Arg("value", PrimitiveTypeNode.int_()),
FunctionNode.Arg("count", PrimitiveTypeNode.int_()),
FunctionNode.Arg("onChange",
CallableTypeNode("TrackbarCallback",
PrimitiveTypeNode.int_("int"))),
]
)
"""
def createButton(buttonName: str,
onChange: Callable[[tuple[int] | tuple[int, Any]], None],
userData: Any | None = ...,
buttonType: int = ...,
initialButtonState: int = ...) -> None: ...
"""
root.add_function(
"createButton",
[
FunctionNode.Arg("buttonName", PrimitiveTypeNode.str_()),
FunctionNode.Arg(
"onChange",
CallableTypeNode(
"ButtonCallback",
UnionTypeNode(
"onButtonChangeCallbackData",
[
TupleTypeNode("onButtonChangeCallbackData",
[PrimitiveTypeNode.int_(), ]),
TupleTypeNode("onButtonChangeCallbackData",
[PrimitiveTypeNode.int_(),
AnyTypeNode("void*")])
]
)
)),
FunctionNode.Arg("userData",
OptionalTypeNode(AnyTypeNode("void*")),
default_value="None"),
FunctionNode.Arg("buttonType", PrimitiveTypeNode.int_(),
default_value="0"),
FunctionNode.Arg("initialButtonState", PrimitiveTypeNode.int_(),
default_value="0")
]
)
"""
def setMouseCallback(
windowName: str,
onMouse: Callback[[int, int, int, int, Any | None], None],
param: Any | None = ...
) -> None: ...
"""
root.add_function(
"setMouseCallback",
[
FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()),
FunctionNode.Arg(
"onMouse",
CallableTypeNode("MouseCallback", [
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.int_(),
OptionalTypeNode(AnyTypeNode("void*"))
])
),
FunctionNode.Arg("param", OptionalTypeNode(AnyTypeNode("void*")),
default_value="None")
]
)
def refine_dnn_module(root: NamespaceNode) -> None:
if "dnn" not in root.namespaces:
return
dnn_module = root.namespaces["dnn"]
"""
class LayerProtocol(Protocol):
def __init__(
self, params: dict[str, DictValue],
blobs: typing.Sequence[cv2.typing.MatLike]
) -> None: ...
def getMemoryShapes(
self, inputs: typing.Sequence[typing.Sequence[int]]
) -> typing.Sequence[typing.Sequence[int]]: ...
def forward(
self, inputs: typing.Sequence[cv2.typing.MatLike]
) -> typing.Sequence[cv2.typing.MatLike]: ...
"""
layer_proto = ProtocolClassNode("LayerProtocol", dnn_module)
layer_proto.add_function(
"__init__",
arguments=[
FunctionNode.Arg(
"params",
DictTypeNode(
"LayerParams", PrimitiveTypeNode.str_(),
create_type_node("cv::dnn::DictValue")
)
),
FunctionNode.Arg("blobs", create_type_node("vector<cv::Mat>"))
]
)
layer_proto.add_function(
"getMemoryShapes",
arguments=[
FunctionNode.Arg("inputs",
create_type_node("vector<vector<int>>"))
],
return_type=FunctionNode.RetType(
create_type_node("vector<vector<int>>")
)
)
layer_proto.add_function(
"forward",
arguments=[
FunctionNode.Arg("inputs", create_type_node("vector<cv::Mat>"))
],
return_type=FunctionNode.RetType(create_type_node("vector<cv::Mat>"))
)
"""
def dnn_registerLayer(layerTypeName: str,
layerClass: typing.Type[LayerProtocol]) -> None: ...
"""
root.add_function(
"dnn_registerLayer",
arguments=[
FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_()),
FunctionNode.Arg(
"layerClass",
ClassTypeNode(ASTNodeTypeNode(
layer_proto.export_name, f"dnn.{layer_proto.export_name}"
))
)
]
)
"""
def dnn_unregisterLayer(layerTypeName: str) -> None: ...
"""
root.add_function(
"dnn_unregisterLayer",
arguments=[
FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_())
]
)
def _trim_class_name_from_argument_types(
overloads: Iterable[FunctionNode.Overload],
class_name: str
) -> None:
separator = f"{class_name}_"
for overload in overloads:
for arg in [arg for arg in overload.arguments
if arg.type_node is not None]:
ast_node = cast(ASTNodeTypeNode, arg.type_node)
if class_name in ast_node.ctype_name:
fixed_name = ast_node._typename.split(separator)[-1]
ast_node._typename = fixed_name
def _find_argument_index(arguments: Sequence[FunctionNode.Arg],
name: str) -> Optional[int]:
for i, arg in enumerate(arguments):
if arg.name == name:
return i
return None
def make_matlike_or_scalar_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]:
"""Make arguments accept both MatLike and Scalar types.
This is used for functions like inRange where the C++ InputArray parameter
can accept both Mat objects and Scalar values (tuples, floats, etc.).
Example: cv2.inRange(img, (0, 0, 0), (255, 255, 255)) should be valid.
"""
def _make_matlike_or_scalar_arg(root_node: NamespaceNode,
function_symbol_name: SymbolName) -> None:
from .predefined_types import PREDEFINED_TYPES
function = find_function_node(root_node, function_symbol_name)
for arg_name in arg_names:
found_overload_with_arg = False
for overload in function.overloads:
arg_idx = _find_argument_index(overload.arguments, arg_name)
# skip overloads without this argument
if arg_idx is None:
continue
current_type = overload.arguments[arg_idx].type_node
# Check if it's already a union or if it already includes Scalar
if isinstance(current_type, UnionTypeNode):
# Check if Scalar is already in the union
has_scalar = any(
isinstance(item, AliasRefTypeNode) and item.typename == "Scalar"
for item in current_type.items
)
if has_scalar:
continue
# Add Scalar to existing union
scalar_ref = AliasRefTypeNode("Scalar")
current_type.items = current_type.items + (scalar_ref,)
else:
# Create a union of current type and Scalar
scalar_ref = AliasRefTypeNode("Scalar")
overload.arguments[arg_idx].type_node = UnionTypeNode(
f"{arg_name}_type",
(cast(TypeNode, current_type), scalar_ref)
)
found_overload_with_arg = True
if not found_overload_with_arg:
raise RuntimeError(
f"Failed to find argument with name: '{arg_name}'"
f" in '{function_symbol_name.name}' overloads"
)
return _make_matlike_or_scalar_arg
NODES_TO_REFINE = {
SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"),
SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"),
SymbolName(("cv", ), (), "floodFill"): make_optional_arg("mask"),
SymbolName(("cv", ), ("Feature2D", ), "detectAndCompute"): make_optional_arg("mask"),
SymbolName(("cv", ), (), "findEssentialMat"): make_optional_arg(
"distCoeffs1", "distCoeffs2", "dist_coeff1", "dist_coeff2"
),
SymbolName(("cv", ), (), "drawFrameAxes"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "getOptimalNewCameraMatrix"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "initInverseRectificationMap"): make_optional_arg("distCoeffs", "R"),
SymbolName(("cv", ), (), "initUndistortRectifyMap"): make_optional_arg("distCoeffs", "R"),
SymbolName(("cv", ), (), "projectPoints"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solveP3P"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solvePnP"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solvePnPGeneric"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solvePnPRansac"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solvePnPRefineLM"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "solvePnPRefineVVS"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "undistort"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "undistortPoints"): make_optional_arg("distCoeffs"),
SymbolName(("cv", ), (), "calibrateCamera"): make_optional_arg("cameraMatrix", "distCoeffs"),
SymbolName(("cv", "fisheye"), (), "initUndistortRectifyMap"): make_optional_arg("D"),
SymbolName(("cv", ), (), "imread"): make_optional_none_return,
SymbolName(("cv", ), (), "imdecode"): make_optional_none_return,
SymbolName(("cv", ), (), "HoughCircles"): make_optional_none_return,
SymbolName(("cv", ), (), "HoughLines"): make_optional_none_return,
SymbolName(("cv", ), (), "HoughLinesP"): make_optional_none_return,
# Fix for issue #28534: inRange should accept Scalar for lowerb and upperb
SymbolName(("cv", ), (), "inRange"): make_matlike_or_scalar_arg("lowerb", "upperb"),
}
ERROR_CLASS_PROPERTIES = (
ClassProperty("code", PrimitiveTypeNode.int_(), False),
ClassProperty("err", PrimitiveTypeNode.str_(), False),
ClassProperty("file", PrimitiveTypeNode.str_(), False),
ClassProperty("func", PrimitiveTypeNode.str_(), False),
ClassProperty("line", PrimitiveTypeNode.int_(), False),
ClassProperty("msg", PrimitiveTypeNode.str_(), False),
)
@@ -0,0 +1,440 @@
from typing import (NamedTuple, Sequence, Tuple, Union, List,
Dict, Callable, Optional, Generator, cast)
import keyword
from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode,
EnumerationNode, ClassProperty, OptionalTypeNode,
TupleTypeNode, PathLikeTypeNode)
from .types_conversion import create_type_node
class ScopeNotFoundError(Exception):
pass
class SymbolNotFoundError(Exception):
pass
class SymbolName(NamedTuple):
namespaces: Tuple[str, ...]
classes: Tuple[str, ...]
name: str
def __str__(self) -> str:
return '(namespace="{}", classes="{}", name="{}")'.format(
'::'.join(self.namespaces),
'::'.join(self.classes),
self.name
)
def __repr__(self) -> str:
return str(self)
@classmethod
def parse(cls, full_symbol_name: str,
known_namespaces: Sequence[str],
symbol_parts_delimiter: str = '.') -> "SymbolName":
"""Performs contextual symbol name parsing into namespaces, classes
and "bare" symbol name.
Args:
full_symbol_name (str): Input string to parse symbol name from.
known_namespaces (Sequence[str]): Collection of namespace that was
met during C++ headers parsing.
symbol_parts_delimiter (str, optional): Delimiter string used to
split `full_symbol_name` string into chunks. Defaults to '.'.
Returns:
SymbolName: Parsed symbol name structure.
>>> SymbolName.parse('cv.ns.Feature', ('cv', 'cv.ns'))
(namespace="cv::ns", classes="", name="Feature")
>>> SymbolName.parse('cv.ns.Feature', ())
(namespace="", classes="cv::ns", name="Feature")
>>> SymbolName.parse('cv.ns.Feature.Params', ('cv', 'cv.ns'))
(namespace="cv::ns", classes="Feature", name="Params")
>>> SymbolName.parse('cv::ns::Feature::Params::serialize',
... known_namespaces=('cv', 'cv.ns'),
... symbol_parts_delimiter='::')
(namespace="cv::ns", classes="Feature::Params", name="serialize")
"""
chunks = full_symbol_name.split(symbol_parts_delimiter)
namespaces, name = chunks[:-1], chunks[-1]
classes: List[str] = []
while len(namespaces) > 0 and '.'.join(namespaces) not in known_namespaces:
classes.insert(0, namespaces.pop())
return SymbolName(tuple(namespaces), tuple(classes), name)
def find_scope(root: NamespaceNode, symbol_name: SymbolName,
create_missing_namespaces: bool = True) -> Union[NamespaceNode, ClassNode]:
"""Traverses down nodes hierarchy to the direct parent of the node referred
by `symbol_name`.
Args:
root (NamespaceNode): Root node of the hierarchy.
symbol_name (SymbolName): Full symbol name to find scope for.
create_missing_namespaces (bool, optional): Set to True to create missing
namespaces while traversing the hierarchy. Defaults to True.
Raises:
ScopeNotFoundError: If direct parent for the node referred by `symbol_name`
can't be found e.g. one of classes doesn't exist.
Returns:
Union[NamespaceNode, ClassNode]: Direct parent for the node referred by
`symbol_name`.
>>> root = NamespaceNode('cv')
>>> algorithm_node = root.add_class('Algorithm')
>>> find_scope(root, SymbolName(('cv', ), ('Algorithm',), 'Params')) == algorithm_node
True
>>> root = NamespaceNode('cv')
>>> scope = find_scope(root, SymbolName(('cv', 'gapi', 'detail'), (), 'function'))
>>> scope.full_export_name
'cv.gapi.detail'
>>> root = NamespaceNode('cv')
>>> scope = find_scope(root, SymbolName(('cv', 'gapi'), ('GOpaque',), 'function'))
Traceback (most recent call last):
...
ast_utils.ScopeNotFoundError: Can't find a scope for 'function', with \
'(namespace="cv::gapi", classes="GOpaque", name="function")', \
because 'GOpaque' class is not registered yet
"""
assert isinstance(root, NamespaceNode), \
'Wrong hierarchy root type: {}'.format(type(root))
assert symbol_name.namespaces[0] == root.name, \
"Trying to find scope for '{}' with root namespace different from: '{}'".format(
symbol_name, root.name
)
scope: Union[NamespaceNode, ClassNode] = root
for namespace in symbol_name.namespaces[1:]:
if namespace not in scope.namespaces: # type: ignore
if not create_missing_namespaces:
raise ScopeNotFoundError(
"Can't find a scope for '{}', with '{}', because namespace"
" '{}' is not created yet and `create_missing_namespaces`"
" flag is set to False".format(
symbol_name.name, symbol_name, namespace
)
)
scope = scope.add_namespace(namespace) # type: ignore
else:
scope = scope.namespaces[namespace] # type: ignore
for class_name in symbol_name.classes:
if class_name not in scope.classes:
raise ScopeNotFoundError(
"Can't find a scope for '{}', with '{}', because '{}' "
"class is not registered yet".format(
symbol_name.name, symbol_name, class_name
)
)
scope = scope.classes[class_name]
return scope
def find_class_node(root: NamespaceNode, class_symbol: SymbolName,
create_missing_namespaces: bool = False) -> ClassNode:
scope = find_scope(root, class_symbol, create_missing_namespaces)
if class_symbol.name not in scope.classes:
raise SymbolNotFoundError(
"Can't find {} in its scope".format(class_symbol)
)
return scope.classes[class_symbol.name]
def find_function_node(root: NamespaceNode, function_symbol: SymbolName,
create_missing_namespaces: bool = False) -> FunctionNode:
scope = find_scope(root, function_symbol, create_missing_namespaces)
if function_symbol.name not in scope.functions:
raise SymbolNotFoundError(
"Can't find {} in its scope".format(function_symbol)
)
return scope.functions[function_symbol.name]
def create_function_node_in_scope(scope: Union[NamespaceNode, ClassNode],
func_info) -> FunctionNode:
def prepare_overload_arguments_and_return_type(variant):
arguments = [] # type: list[FunctionNode.Arg]
# Enumerate is required, because `argno` in `variant.py_arglist`
# refers to position of argument in C++ function interface,
# but `variant.py_noptargs` refers to position in `py_arglist`
for i, (_, argno) in enumerate(variant.py_arglist):
arg_info = variant.args[argno]
type_node = create_type_node(arg_info.tp)
# Special handling for string representation of the file system path
if arg_info.pathlike and type_node.typename == "str":
type_node = PathLikeTypeNode.string_or_pathlike_()
default_value = None
if len(arg_info.defval):
default_value = arg_info.defval
# If argument is optional and can be None - make its type optional
if variant.is_arg_optional(i):
# NOTE: should UMat be always mandatory for better type hints?
# otherwise overload won't be selected e.g. VideoCapture.read()
if arg_info.py_outputarg:
type_node = OptionalTypeNode(type_node)
default_value = "None"
elif arg_info.isbig() and "None" not in type_node.typename:
# but avoid duplication of the optioness
type_node = OptionalTypeNode(type_node)
arguments.append(
FunctionNode.Arg(arg_info.export_name, type_node=type_node,
default_value=default_value)
)
if func_info.isconstructor:
return arguments, None
# Function has more than 1 output argument, so its return type is a tuple
if len(variant.py_outlist) > 1:
ret_types = []
# Actual returned value of the function goes first
if variant.py_outlist[0][1] == -1:
ret_types.append(create_type_node(variant.rettype))
outlist = variant.py_outlist[1:]
else:
outlist = variant.py_outlist
for _, argno in outlist:
assert argno >= 0, \
f"Logic Error! Outlist contains function return type: {outlist}"
ret_types.append(create_type_node(variant.args[argno].tp))
return arguments, FunctionNode.RetType(
TupleTypeNode("return_type", ret_types)
)
# Function with 1 output argument in Python
if len(variant.py_outlist) == 1:
# Can be represented as a function with a non-void return type in C++
if variant.rettype:
return arguments, FunctionNode.RetType(
create_type_node(variant.rettype)
)
# or a function with void return type and output argument type
# such non-const reference
ret_type = variant.args[variant.py_outlist[0][1]].tp
return arguments, FunctionNode.RetType(
create_type_node(ret_type)
)
# Function without output types returns None in Python
return arguments, None
function_node = FunctionNode(func_info.name)
function_node.parent = scope
if func_info.isconstructor:
function_node.export_name = "__init__"
for variant in func_info.variants:
arguments, ret_type = prepare_overload_arguments_and_return_type(variant)
if isinstance(scope, ClassNode):
if func_info.is_static:
if ret_type is not None and ret_type.typename.endswith(scope.name):
function_node.is_classmethod = True
arguments.insert(0, FunctionNode.Arg("cls"))
else:
function_node.is_static = True
else:
arguments.insert(0, FunctionNode.Arg("self"))
function_node.add_overload(arguments, ret_type)
return function_node
def create_function_node(root: NamespaceNode, func_info) -> FunctionNode:
func_symbol_name = SymbolName(
func_info.namespace.split(".") if len(func_info.namespace) else (),
func_info.classname.split(".") if len(func_info.classname) else (),
func_info.name
)
return create_function_node_in_scope(find_scope(root, func_symbol_name),
func_info)
def create_class_node_in_scope(scope: Union[NamespaceNode, ClassNode],
symbol_name: SymbolName,
class_info) -> ClassNode:
properties = []
for property in class_info.props:
export_property_name = property.name
if keyword.iskeyword(export_property_name):
export_property_name += "_"
properties.append(
ClassProperty(
name=export_property_name,
type_node=create_type_node(property.tp),
is_readonly=property.readonly
)
)
class_node = scope.add_class(symbol_name.name,
properties=properties)
class_node.export_name = class_info.export_name
if class_info.constructor is not None:
create_function_node_in_scope(class_node, class_info.constructor)
for method in class_info.methods.values():
create_function_node_in_scope(class_node, method)
return class_node
def create_class_node(root: NamespaceNode, class_info,
namespaces: Sequence[str]) -> ClassNode:
symbol_name = SymbolName.parse(class_info.full_original_name, namespaces)
scope = find_scope(root, symbol_name)
return create_class_node_in_scope(scope, symbol_name, class_info)
def resolve_enum_scopes(root: NamespaceNode,
enums: Dict[SymbolName, EnumerationNode]):
"""Attaches all enumeration nodes to the appropriate classes and modules
If classes containing enumeration can't be found in the AST - they will
be created and marked as not exportable. This behavior is required to cover
cases, when enumeration is defined in base class, but only its derivatives
are used. Example:
```cpp
class CV_EXPORTS TermCriteria {
public:
enum Type { /* ... */ };
// ...
};
```
Args:
root (NamespaceNode): root of the reconstructed AST
enums (Dict[SymbolName, EnumerationNode]): Mapping between enumerations
symbol names and corresponding nodes without parents.
"""
for symbol_name, enum_node in enums.items():
if symbol_name.classes:
try:
scope = find_scope(root, symbol_name)
except ScopeNotFoundError:
# Scope can't be found if enumeration is a part of class
# that is not exported.
# Create class node, but mark it as not exported
for i, class_name in enumerate(symbol_name.classes):
scope = find_scope(root,
SymbolName(symbol_name.namespaces,
classes=symbol_name.classes[:i],
name=class_name))
if class_name in scope.classes:
continue
class_node = scope.add_class(class_name)
class_node.is_exported = False
scope = find_scope(root, symbol_name)
else:
scope = find_scope(root, symbol_name)
enum_node.parent = scope
def get_enclosing_namespace(
node: ASTNode,
class_node_callback: Optional[Callable[[ClassNode], None]] = None
) -> NamespaceNode:
"""Traverses up nodes hierarchy to find closest enclosing namespace of the
passed node
Args:
node (ASTNode): Node to find a namespace for.
class_node_callback (Optional[Callable[[ClassNode], None]]): Optional
callable object invoked for each traversed class node in bottom-up
order. Defaults: None.
Returns:
NamespaceNode: Closest enclosing namespace of the provided node.
Raises:
AssertionError: if nodes hierarchy missing a namespace node.
>>> root = NamespaceNode('cv')
>>> feature_class = root.add_class("Feature")
>>> get_enclosing_namespace(feature_class) == root
True
>>> root = NamespaceNode('cv')
>>> feature_class = root.add_class("Feature")
>>> feature_params_class = feature_class.add_class("Params")
>>> serialize_params_func = feature_params_class.add_function("serialize")
>>> get_enclosing_namespace(serialize_params_func) == root
True
>>> root = NamespaceNode('cv')
>>> detail_ns = root.add_namespace('detail')
>>> flags_enum = detail_ns.add_enumeration('Flags')
>>> get_enclosing_namespace(flags_enum) == detail_ns
True
"""
parent_node = node.parent
while not isinstance(parent_node, NamespaceNode):
assert parent_node is not None, \
"Can't find enclosing namespace for '{}' known as: '{}'".format(
node.full_export_name, node.native_name
)
if class_node_callback:
class_node_callback(cast(ClassNode, parent_node))
parent_node = parent_node.parent
return parent_node
def get_enum_module_and_export_name(enum_node: EnumerationNode) -> Tuple[str, str]:
"""Get export name of the enum node with its module name.
Note: Enumeration export names are prefixed with enclosing class names.
Args:
enum_node (EnumerationNode): Enumeration node to construct name for.
Returns:
Tuple[str, str]: a pair of enum export name and its full module name.
"""
enum_export_name = enum_node.export_name
def update_full_export_name(class_node: ClassNode) -> None:
nonlocal enum_export_name
enum_export_name = class_node.export_name + "_" + enum_export_name
namespace_node = get_enclosing_namespace(enum_node,
update_full_export_name)
return enum_export_name, namespace_node.full_export_name
def for_each_class(
node: Union[NamespaceNode, ClassNode]
) -> Generator[ClassNode, None, None]:
for cls in node.classes.values():
yield cls
if len(cls.classes):
yield from for_each_class(cls)
def for_each_function(
node: Union[NamespaceNode, ClassNode],
traverse_class_nodes: bool = True
) -> Generator[FunctionNode, None, None]:
yield from node.functions.values()
if traverse_class_nodes:
for cls in for_each_class(node):
yield from for_each_function(cls)
def for_each_function_overload(
node: Union[NamespaceNode, ClassNode],
traverse_class_nodes: bool = True
) -> Generator[FunctionNode.Overload, None, None]:
for func in for_each_function(node, traverse_class_nodes):
yield from func.overloads
if __name__ == '__main__':
import doctest
doctest.testmod()
@@ -0,0 +1,864 @@
__all__ = ("generate_typing_stubs", )
from io import StringIO
from pathlib import Path
import re
import shutil
from typing import (Callable, NamedTuple, Union, Set, Dict,
Collection, Tuple, List)
import warnings
from .ast_utils import (get_enclosing_namespace,
get_enum_module_and_export_name,
for_each_function_overload,
for_each_class)
from .predefined_types import PREDEFINED_TYPES
from .api_refinement import apply_manual_api_refinement
from .nodes import (ASTNode, ASTNodeType, NamespaceNode, ClassNode,
FunctionNode, EnumerationNode, ConstantNode,
ProtocolClassNode)
from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode,
AggregatedTypeNode, ASTNodeTypeNode,
ConditionalAliasTypeNode, PrimitiveTypeNode)
def _clean_stale_stubs_dirs(stubs_root: Path) -> None:
"""Remove all subdirectories under stubs_root.
During incremental builds, disabling a previously enabled module leaves
behind its typing stub directory (e.g. cv2/gapi/). Removing all
subdirectories before regeneration ensures only stubs for currently
enabled modules are present. Top-level files (py.typed, __init__.pyi)
are kept because they are managed separately.
"""
if not stubs_root.is_dir():
return
for item in stubs_root.iterdir():
if item.is_dir():
shutil.rmtree(item)
def generate_typing_stubs(root: NamespaceNode, output_path: Path):
"""Generates typing stubs for the AST with root `root` and outputs
created files tree to directory pointed by `output_path`.
Stubs generation consist from 4 steps:
1. Reconstruction of AST tree for header parser output.
2. "Lazy" AST nodes resolution (type nodes used as function arguments
and return types). Resolution procedure attaches every "lazy"
AST node to the corresponding node in the AST created during step 1.
3. Generation of the typing module content. Typing module doesn't exist
in library code, but is essential place to define aliases widely used
in stub files.
4. Generation of typing stubs from the reconstructed AST.
Every namespace corresponds to a Python module with the same name.
Generation procedure is recursive repetition of the following steps
for each namespace (module):
- Collect and write required imports for the module
- Write all module constants stubs
- Write all module enumerations stubs
- Write all module classes stubs, preserving correct declaration
order, when base classes go before their derivatives.
- Write all module functions stubs
- Repeat steps above for nested namespaces
Args:
root (NamespaceNode): Root namespace node of the library AST.
output_path (Path): Path to output directory.
"""
# Perform special handling for function arguments that has some conventions
# not expressed in their API e.g. optionality of mutually exclusive arguments
# without default values:
# ```cxx
# cv::resize(cv::InputArray src, cv::OutputArray dst, cv::Size dsize,
# double fx = 0.0, double fy = 0.0, int interpolation);
# ```
# should accept `None` as `dsize`:
# ```python
# cv2.resize(image, dsize=None, fx=0.5, fy=0.5)
# ```
apply_manual_api_refinement(root)
# Most of the time type nodes miss their full name (especially function
# arguments and return types), so resolution should start from the narrowest
# scope and gradually expanded.
# Example:
# ```cpp
# namespace cv {
# enum AlgorithmType {
# // ...
# };
# namespace detail {
# struct Algorithm {
# static Ptr<Algorithm> create(AlgorithmType alg_type);
# };
# } // namespace detail
# } // namespace cv
# ```
# To resolve `alg_type` argument of function `create` having `AlgorithmType`
# type from above example the following steps are done:
# 1. Try to resolve against `cv::detail::Algorithm` - fail
# 2. Try to resolve against `cv::detail` - fail
# 3. Try to resolve against `cv` - success
# The whole process should fail !only! when all possible scopes are
# checked and at least 1 node is still unresolved.
root.resolve_type_nodes()
# Remove stale typing stub subdirectories from previous builds.
# In incremental builds, disabling a module (e.g. -DBUILD_opencv_gapi=OFF)
# no longer generates its stubs, but leftover directories from a previous
# build persist and propagate through the copy/install steps, causing
# type-checker errors for stubs referencing unavailable modules.
_clean_stale_stubs_dirs(Path(output_path) / root.export_name)
_generate_typing_module(root, output_path)
_populate_reexported_symbols(root)
_generate_typing_stubs(root, output_path)
def _generate_typing_stubs(root: NamespaceNode, output_path: Path) -> None:
output_path = Path(output_path) / root.export_name
output_path.mkdir(parents=True, exist_ok=True)
# Collect all imports required for module items declaration
required_imports = _collect_required_imports(root)
output_stream = StringIO()
# Add empty __all__ dunder on top of the module
output_stream.write("__all__: list[str] = []\n\n")
# Write required imports at the top of file
_write_required_imports(required_imports, output_stream)
_write_reexported_symbols_section(root, output_stream)
# NOTE: Enumerations require special handling, because all enumeration
# constants are exposed as module attributes
has_enums = _generate_section_stub(
StubSection("# Enumerations", ASTNodeType.Enumeration), root,
output_stream, 0
)
# Collect all enums from class level and export them to module level
for class_node in root.classes.values():
if _generate_enums_from_classes_tree(class_node, output_stream,
indent=0):
has_enums = True
# 2 empty lines between enum and classes definitions
if has_enums:
output_stream.write("\n")
# Write the rest of module content - classes and functions
for section in STUB_SECTIONS:
_generate_section_stub(section, root, output_stream, 0)
# Dump content to the output file
(output_path / "__init__.pyi").write_text(output_stream.getvalue())
# Process nested namespaces
for ns in root.namespaces.values():
_generate_typing_stubs(ns, output_path)
class StubSection(NamedTuple):
name: str
node_type: ASTNodeType
STUB_SECTIONS = (
StubSection("# Constants", ASTNodeType.Constant),
# Enumerations are skipped due to special handling rules
# StubSection("# Enumerations", ASTNodeType.Enumeration),
StubSection("# Classes", ASTNodeType.Class),
StubSection("# Functions", ASTNodeType.Function)
)
def _generate_section_stub(section: StubSection, node: ASTNode,
output_stream: StringIO, indent: int) -> bool:
"""Generates stub for a single type of children nodes of the provided node.
Args:
section (StubSection): section identifier that carries section name and
type its nodes.
node (ASTNode): root node with children nodes used for
output_stream (StringIO): Output stream for all nodes stubs related to
the given section.
indent (int): Indent used for each line written to `output_stream`.
Returns:
bool: `True` if section has a content, `False` otherwise.
"""
if section.node_type not in node._children:
return False
children = node._children[section.node_type]
if len(children) == 0:
return False
output_stream.write(" " * indent)
output_stream.write(section.name)
output_stream.write("\n")
stub_generator = NODE_TYPE_TO_STUB_GENERATOR[section.node_type]
children = filter(lambda c: c.is_exported, children.values()) # type: ignore
if hasattr(section.node_type, "weight"):
children = sorted(children, key=lambda child: getattr(child, "weight")) # type: ignore
for child in children:
stub_generator(child, output_stream, indent) # type: ignore
output_stream.write("\n")
return True
def _generate_class_stub(class_node: ClassNode, output_stream: StringIO,
indent: int = 0) -> None:
"""Generates stub for the provided class node.
Rules:
- Read/write properties are converted to object attributes.
- Readonly properties are converted to functions decorated with `@property`.
- When return type of static functions matches class name - these functions
are treated as factory functions and annotated with `@classmethod`.
- In contrast to implicit `this` argument in C++ methods, in Python all
"normal" methods have explicit `self` as their first argument.
- Body of empty classes is replaced with `...`
Example:
```cpp
struct Object : public BaseObject {
struct InnerObject {
int param;
bool param2;
float readonlyParam();
};
Object(int param, bool param2 = false);
Object(InnerObject obj);
static Object create();
};
```
becomes
```python
class Object(BaseObject):
class InnerObject:
param: int
param2: bool
@property
def readonlyParam() -> float: ...
@typing.override
def __init__(self, param: int, param2: bool = ...) -> None: ...
@typing.override
def __init__(self, obj: "Object.InnerObject") -> None: ...
@classmethod
def create(cls) -> Object: ...
```
Args:
class_node (ClassNode): Class node to generate stub entry for.
output_stream (StringIO): Output stream for class stub.
indent (int, optional): Indent used for each line written to
`output_stream`. Defaults to 0.
"""
class_module = get_enclosing_namespace(class_node)
class_module_name = class_module.full_export_name
if len(class_node.bases) > 0:
bases = []
for base in class_node.bases:
base_module = get_enclosing_namespace(base) # type: ignore
if base_module != class_module:
bases.append(base.full_export_name)
else:
bases.append(base.export_name)
inheritance_str = f"({', '.join(bases)})"
elif isinstance(class_node, ProtocolClassNode):
inheritance_str = "(Protocol)"
else:
inheritance_str = ""
output_stream.write(
"{indent}class {name}{bases}:\n".format(
indent=" " * indent,
name=class_node.export_name,
bases=inheritance_str
)
)
has_content = len(class_node.properties) > 0
# Processing class properties
for property in class_node.properties:
if property.is_readonly:
template = "{indent}@property\n{indent}def {name}(self) -> {type}: ...\n"
else:
template = "{indent}{name}: {type}\n"
output_stream.write(
template.format(indent=" " * (indent + 4),
name=property.name,
type=property.relative_typename(class_module_name))
)
if len(class_node.properties) > 0:
output_stream.write("\n")
for section in STUB_SECTIONS:
if _generate_section_stub(section, class_node,
output_stream, indent + 4):
has_content = True
if not has_content:
output_stream.write(" " * (indent + 4))
output_stream.write("...\n\n")
def _generate_constant_stub(constant_node: ConstantNode,
output_stream: StringIO, indent: int = 0,
extra_export_prefix: str = "",
generate_uppercase_version: bool = True) -> Tuple[str, ...]:
"""Generates stub for the provided constant node.
Args:
constant_node (ConstantNode): Constant node to generate stub entry for.
output_stream (StringIO): Output stream for constant stub.
indent (int, optional): Indent used for each line written to
`output_stream`. Defaults to 0.
extra_export_prefix (str, optional): Extra prefix added to the export
constant name. Defaults to empty string.
generate_uppercase_version (bool, optional): Generate uppercase version
alongside the normal one. Defaults to True.
Returns:
Tuple[str, ...]: exported constants names.
"""
def write_constant_to_stream(export_name: str) -> None:
output_stream.write(
"{indent}{name}: {value_type}\n".format(
name=export_name,
value_type=constant_node.value_type,
indent=" " * indent
)
)
export_name = extra_export_prefix + constant_node.export_name
write_constant_to_stream(export_name)
if generate_uppercase_version:
# Handle Python "magic" constants like __version__
if re.match(r"^__.*__$", export_name) is not None:
return export_name,
uppercase_name = re.sub(r"([a-z])([A-Z])", r"\1_\2", export_name).upper()
if export_name != uppercase_name:
write_constant_to_stream(uppercase_name)
return export_name, uppercase_name
return export_name,
def _generate_enumeration_stub(enumeration_node: EnumerationNode,
output_stream: StringIO, indent: int = 0,
extra_export_prefix: str = "") -> None:
"""Generates stub for the provided enumeration node. In contrast to the
Python `enum.Enum` class, C++ enumerations are exported as module-level
(or class-level) constants.
Example:
```cpp
enum Flags {
Flag1 = 0,
Flag2 = 1,
Flag3
};
```
becomes
```python
Flag1: int
Flag2: int
Flag3: int
Flags = int # One of [Flag1, Flag2, Flag3]
```
Unnamed enumerations don't export their names to Python:
```cpp
enum {
Flag1 = 0,
Flag2 = 1
};
```
becomes
```python
Flag1: int
Flag2: int
```
Scoped enumeration adds its name before each item name:
```cpp
enum struct ScopedEnum {
Flag1,
Flag2
};
```
becomes
```python
ScopedEnum_Flag1: int
ScopedEnum_Flag2: int
ScopedEnum = int # One of [ScopedEnum_Flag1, ScopedEnum_Flag2]
```
Args:
enumeration_node (EnumerationNode): Enumeration node to generate stub entry for.
output_stream (StringIO): Output stream for enumeration stub.
indent (int, optional): Indent used for each line written to `output_stream`.
Defaults to 0.
extra_export_prefix (str, optional) Extra prefix added to the export
enumeration name. Defaults to empty string.
"""
entries_extra_prefix = extra_export_prefix
if enumeration_node.is_scoped:
entries_extra_prefix += enumeration_node.export_name + "_"
generated_constants_entries: List[str] = []
for entry in enumeration_node.constants.values():
generated_constants_entries.extend(
_generate_constant_stub(entry, output_stream, indent, entries_extra_prefix)
)
# Unnamed enumerations are skipped as definition
if enumeration_node.export_name.endswith("<unnamed>"):
output_stream.write("\n")
return
output_stream.write(
'{indent}{export_prefix}{name} = int\n{indent}"""One of [{entries}]"""\n\n'.format(
export_prefix=extra_export_prefix,
name=enumeration_node.export_name,
entries=", ".join(generated_constants_entries),
indent=" " * indent
)
)
def _generate_function_stub(function_node: FunctionNode,
output_stream: StringIO, indent: int = 0) -> None:
"""Generates stub entry for the provided function node. Function node can
refer free function or class method.
Args:
function_node (FunctionNode): Function node to generate stub entry for.
output_stream (StringIO): Output stream for function stub.
indent (int, optional): Indent used for each line written to
`output_stream`. Defaults to 0.
"""
# Function is a stub without any arguments information
if not function_node.overloads:
warnings.warn(
'Function node "{}" exported as "{}" has no overloads'.format(
function_node.full_name, function_node.full_export_name
)
)
return
decorators = []
if function_node.is_classmethod:
decorators.append(" " * indent + "@classmethod")
elif function_node.is_static:
decorators.append(" " * indent + "@staticmethod")
if len(function_node.overloads) > 1:
decorators.append(" " * indent + "@_typing.overload")
function_module = get_enclosing_namespace(function_node)
function_module_name = function_module.full_export_name
for overload in function_node.overloads:
# Annotate every function argument
annotated_args = []
for arg in overload.arguments:
annotated_arg = arg.name
typename = arg.relative_typename(function_module_name)
if typename is not None:
annotated_arg += ": " + typename
if arg.default_value is not None:
annotated_arg += " = ..."
annotated_args.append(annotated_arg)
# And convert return type to the actual type
if overload.return_type is not None:
ret_type = overload.return_type.relative_typename(function_module_name)
else:
ret_type = "None"
output_stream.write(
"{decorators}"
"{indent}def {name}({args}) -> {ret_type}: ...\n".format(
decorators="\n".join(decorators) +
"\n" if len(decorators) > 0 else "",
name=function_node.export_name,
args=", ".join(annotated_args),
ret_type=ret_type,
indent=" " * indent
)
)
output_stream.write("\n")
def _generate_enums_from_classes_tree(class_node: ClassNode,
output_stream: StringIO,
indent: int = 0,
class_name_prefix: str = "") -> bool:
"""Recursively generates class-level enumerations on the module level
starting from the `class_node`.
NOTE: This function is required, because all enumerations are exported as
module-level constants.
Example:
```cpp
namespace cv {
struct TermCriteria {
enum Type {
COUNT = 1,
MAX_ITER = COUNT,
EPS = 2
};
};
} // namespace cv
```
is exported to `__init__.pyi` of `cv` module as as
```python
TermCriteria_COUNT: int
TermCriteria_MAX_ITER: int
TermCriteria_EPS: int
TermCriteria_Type = int # One of [COUNT, MAX_ITER, EPS]
```
Args:
class_node (ClassNode): Class node to generate enumerations stubs for.
output_stream (StringIO): Output stream for enumerations stub.
indent (int, optional): Indent used for each line written to
`output_stream`. Defaults to 0.
class_name_prefix (str, optional): Prefix used for enumerations and
constants names. Defaults to "".
Returns:
bool: `True` if classes tree declares at least 1 enum, `False` otherwise.
"""
class_name_prefix = class_node.export_name + "_" + class_name_prefix
has_content = len(class_node.enumerations) > 0
for enum_node in class_node.enumerations.values():
_generate_enumeration_stub(enum_node, output_stream, indent,
class_name_prefix)
for cls in class_node.classes.values():
if _generate_enums_from_classes_tree(cls, output_stream, indent,
class_name_prefix):
has_content = True
return has_content
def check_overload_presence(node: Union[NamespaceNode, ClassNode]) -> bool:
"""Checks that node has at least 1 function with overload.
Args:
node (Union[NamespaceNode, ClassNode]): Node to check for overload
presence.
Returns:
bool: True if input node has at least 1 function with overload, False
otherwise.
"""
for func_node in node.functions.values():
if len(func_node.overloads) > 1:
return True
return False
def _collect_required_imports(root: NamespaceNode) -> Collection[str]:
"""Collects all imports required for classes and functions typing stubs
declarations.
Args:
root (NamespaceNode): Namespace node to collect imports for
Returns:
Collection[str]: Collection of unique `import smth` statements required
for classes and function declarations of `root` node.
"""
def _add_required_usage_imports(type_node: TypeNode, imports: Set[str]):
for required_import in type_node.required_usage_imports:
imports.add(required_import)
required_imports: Set[str] = set()
# Check if typing module is required due to @overload decorator usage
# Looking for module-level function with at least 1 overload
has_overload = check_overload_presence(root)
# if there is no module-level functions with overload, check its presence
# during class traversing, including their inner-classes
has_protocol = False
for cls in for_each_class(root):
if not has_overload and check_overload_presence(cls):
has_overload = True
required_imports.add("import typing as _typing")
# Add required imports for class properties
for prop in cls.properties:
_add_required_usage_imports(prop.type_node, required_imports)
# Add required imports for class bases
for base in cls.bases:
base_namespace = get_enclosing_namespace(base) # type: ignore
if base_namespace != root:
required_imports.add(
"import " + base_namespace.full_export_name
)
if isinstance(cls, ProtocolClassNode):
has_protocol = True
if has_overload:
required_imports.add("import typing as _typing")
# Importing modules required to resolve functions arguments
for overload in for_each_function_overload(root):
for arg in filter(lambda a: a.type_node is not None,
overload.arguments):
_add_required_usage_imports(arg.type_node, required_imports) # type: ignore
if overload.return_type is not None:
_add_required_usage_imports(overload.return_type.type_node,
required_imports)
root_import = "import " + root.full_export_name
if root_import in required_imports:
required_imports.remove(root_import)
if has_protocol:
required_imports.add("import sys")
ordered_required_imports = sorted(required_imports)
# Protocol import always goes as last import statement
if has_protocol:
ordered_required_imports.append(
"""if sys.version_info >= (3, 8):
from typing import Protocol
else:
from typing_extensions import Protocol"""
)
return ordered_required_imports
def _populate_reexported_symbols(root: NamespaceNode) -> None:
# Re-export all submodules to allow referencing symbols in submodules
# without submodule import. Example:
# `cv2.aruco.ArucoDetector` should be accessible without `import cv2.aruco`
def _reexport_submodule(ns: NamespaceNode) -> None:
for submodule in ns.namespaces.values():
ns.reexported_submodules.append(submodule.export_name)
_reexport_submodule(submodule)
_reexport_submodule(root)
root.reexported_submodules.append("typing")
# Special cases, symbols defined in possible pure Python submodules
# should be
root.reexported_submodules_symbols["mat_wrapper"].append("Mat")
def _write_reexported_symbols_section(module: NamespaceNode,
output_stream: StringIO) -> None:
"""Write re-export section for the given module.
Re-export statements have from `from module_name import smth as smth`.
Example:
```python
from cv2 import aruco as aruco
from cv2 import cuda as cuda
from cv2 import ml as ml
from cv2.mat_wrapper import Mat as Mat
```
Args:
module (NamespaceNode): Module with re-exported symbols.
output_stream (StringIO): Output stream for re-export statements.
"""
parent_name = module.full_export_name
for submodule in sorted(module.reexported_submodules):
output_stream.write(
"from {0} import {1} as {1}\n".format(parent_name, submodule)
)
for submodule, symbols in sorted(module.reexported_submodules_symbols.items(),
key=lambda kv: kv[0]):
for symbol in symbols:
output_stream.write(
"from {0}.{1} import {2} as {2}\n".format(
parent_name, submodule, symbol
)
)
if len(module.reexported_submodules) or \
len(module.reexported_submodules_symbols):
output_stream.write("\n\n")
def _write_required_imports(required_imports: Collection[str],
output_stream: StringIO) -> None:
"""Writes all entries of `required_imports` to the `output_stream`.
Args:
required_imports (Collection[str]): Imports to write into the output
stream.
output_stream (StringIO): Output stream for import statements.
"""
for required_import in required_imports:
output_stream.write(required_import)
output_stream.write("\n")
if len(required_imports):
output_stream.write("\n\n")
def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
"""Generates stub file for typings module.
Actual module doesn't exist, but it is an appropriate place to define
all widely-used aliases.
Args:
root (NamespaceNode): AST root node used for type nodes resolution.
output_path (Path): Path to typing module directory, where __init__.pyi
will be written.
"""
def has_all_required_modules(type_node: TypeNode) -> bool:
return all(em in root.namespaces for em in type_node.required_modules)
def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
assert isinstance(type_node, AggregatedTypeNode), \
f"Provided type node '{type_node.ctype_name}' is not an aggregated type"
for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node):
type_node = PREDEFINED_TYPES[item.ctype_name]
if isinstance(type_node, AliasTypeNode):
register_alias(type_node)
elif isinstance(type_node, ConditionalAliasTypeNode):
conditional_type_nodes[type_node.ctype_name] = type_node
def create_alias_for_enum_node(enum_node_alias: AliasTypeNode) -> ConditionalAliasTypeNode:
"""Create conditional int alias corresponding to the given enum node.
Args:
enum_node (AliasTypeNode): Enumeration node to create conditional
int alias for.
Returns:
ConditionalAliasTypeNode: conditional int alias node with same
export name as enum.
"""
enum_node = enum_node_alias.ast_node
assert enum_node.node_type == ASTNodeType.Enumeration, \
f"{enum_node} has wrong node type. Expected type: Enumeration."
enum_export_name, enum_module_name = get_enum_module_and_export_name(
enum_node
)
return ConditionalAliasTypeNode(
enum_export_name,
"_typing.TYPE_CHECKING",
positive_branch_type=enum_node_alias,
negative_branch_type=PrimitiveTypeNode.int_(enum_export_name),
condition_required_imports=("import typing as _typing", )
)
def register_alias(alias_node: AliasTypeNode) -> None:
typename = alias_node.typename
# Check if alias is already registered
if typename in aliases:
return
# Collect required imports for alias definition
for required_import in alias_node.required_definition_imports:
required_imports.add(required_import)
if isinstance(alias_node.value, AggregatedTypeNode):
# Check if collection contains a link to another alias
register_alias_links_from_aggregated_type(alias_node.value)
# Remove references to alias nodes
for i, item in enumerate(alias_node.value.items):
# Process enumerations only
if not isinstance(item, ASTNodeTypeNode) or item.ast_node is None:
continue
if item.ast_node.node_type != ASTNodeType.Enumeration:
continue
enum_node = create_alias_for_enum_node(item)
alias_node.value.items[i] = enum_node
conditional_type_nodes[enum_node.ctype_name] = enum_node
if isinstance(alias_node.value, ASTNodeTypeNode) \
and alias_node.value.ast_node == ASTNodeType.Enumeration:
enum_node = create_alias_for_enum_node(alias_node.ast_node)
conditional_type_nodes[enum_node.ctype_name] = enum_node
return
# Strip module prefix from aliased types
aliases[typename] = alias_node.value.full_typename.replace(
root.export_name + ".typing.", ""
)
if alias_node.doc is not None:
aliases[typename] += f'\n"""{alias_node.doc}"""'
output_path = Path(output_path) / root.export_name / "typing"
output_path.mkdir(parents=True, exist_ok=True)
required_imports: Set[str] = set()
aliases: Dict[str, str] = {}
conditional_type_nodes: Dict[str, ConditionalAliasTypeNode] = {}
# Resolve each node and register aliases
TypeNode.compatible_to_runtime_usage = True
for node in PREDEFINED_TYPES.values():
# if node does not have at least one required module skip it
# e.g. GArgs requires G-API module, so if build without G-API GArgs is not included
if not has_all_required_modules(node):
continue
node.resolve(root)
if isinstance(node, AliasTypeNode):
register_alias(node)
elif isinstance(node, ConditionalAliasTypeNode):
conditional_type_nodes[node.ctype_name] = node
for node in conditional_type_nodes.values():
for required_import in node.required_definition_imports:
required_imports.add(required_import)
output_stream = StringIO()
output_stream.write("__all__ = [\n")
for alias_name in aliases:
output_stream.write(f' "{alias_name}",\n')
output_stream.write("]\n\n")
_write_required_imports(required_imports, output_stream)
# Add type checking time definitions as generated __init__.py content
for _, type_node in conditional_type_nodes.items():
output_stream.write(f"if {type_node.condition}:\n ")
output_stream.write(f"{type_node.typename} = {type_node.positive_branch_type.full_typename}\nelse:\n")
output_stream.write(f" {type_node.typename} = {type_node.negative_branch_type.full_typename}\n\n\n")
for alias_name, alias_type in aliases.items():
output_stream.write(f"{alias_name} = {alias_type}\n")
TypeNode.compatible_to_runtime_usage = False
(output_path / "__init__.py").write_text(output_stream.getvalue())
StubGenerator = Callable[[ASTNode, StringIO, int], None]
NODE_TYPE_TO_STUB_GENERATOR = {
ASTNodeType.Class: _generate_class_stub,
ASTNodeType.Constant: _generate_constant_stub,
ASTNodeType.Enumeration: _generate_enumeration_stub,
ASTNodeType.Function: _generate_function_stub
}
@@ -0,0 +1,12 @@
from .node import ASTNode, ASTNodeType
from .namespace_node import NamespaceNode
from .class_node import ClassNode, ClassProperty, ProtocolClassNode
from .function_node import FunctionNode
from .enumeration_node import EnumerationNode
from .constant_node import ConstantNode
from .type_node import (
TypeNode, OptionalTypeNode, UnionTypeNode, NoneTypeNode, TupleTypeNode,
ASTNodeTypeNode, AliasTypeNode, SequenceTypeNode, AnyTypeNode,
AggregatedTypeNode, NDArrayTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
CallableTypeNode, DictTypeNode, ClassTypeNode, PathLikeTypeNode
)
@@ -0,0 +1,190 @@
from typing import Type, Sequence, NamedTuple, Optional, Tuple, Dict
import itertools
import weakref
from .node import ASTNode, ASTNodeType
from .function_node import FunctionNode
from .enumeration_node import EnumerationNode
from .constant_node import ConstantNode
from .type_node import TypeNode, TypeResolutionError
class ClassProperty(NamedTuple):
name: str
type_node: TypeNode
is_readonly: bool
@property
def typename(self) -> str:
return self.type_node.full_typename
def resolve_type_nodes(self, root: ASTNode) -> None:
try:
self.type_node.resolve(root)
except TypeResolutionError as e:
raise TypeResolutionError(
'Failed to resolve "{}" property'.format(self.name)
) from e
def relative_typename(self, full_node_name: str) -> str:
"""Typename relative to the passed AST node name.
Args:
full_node_name (str): Full export name of the AST node
Returns:
str: typename relative to the passed AST node name
"""
return self.type_node.relative_typename(full_node_name)
class ClassNode(ASTNode):
"""Represents a C++ class that is also a class in Python.
ClassNode can have functions (methods), enumerations, constants and other
classes as its children nodes.
Class properties are not treated as a part of AST for simplicity and have
extra handling if required.
"""
def __init__(self, name: str, parent: Optional[ASTNode] = None,
export_name: Optional[str] = None,
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
properties: Sequence[ClassProperty] = ()) -> None:
super().__init__(name, parent, export_name)
self.bases = list(bases)
self.properties = properties
@property
def weight(self) -> int:
return 1 + sum(base.weight for base in self.bases)
@property
def children_types(self) -> Tuple[ASTNodeType, ...]:
return (ASTNodeType.Class, ASTNodeType.Function,
ASTNodeType.Enumeration, ASTNodeType.Constant)
@property
def node_type(self) -> ASTNodeType:
return ASTNodeType.Class
@property
def classes(self) -> Dict[str, "ClassNode"]:
return self._children[ASTNodeType.Class]
@property
def functions(self) -> Dict[str, FunctionNode]:
return self._children[ASTNodeType.Function]
@property
def enumerations(self) -> Dict[str, EnumerationNode]:
return self._children[ASTNodeType.Enumeration]
@property
def constants(self) -> Dict[str, ConstantNode]:
return self._children[ASTNodeType.Constant]
def add_class(self, name: str,
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
properties: Sequence[ClassProperty] = ()) -> "ClassNode":
return self._add_child(ClassNode, name, bases=bases,
properties=properties)
def add_function(self, name: str, arguments: Sequence[FunctionNode.Arg] = (),
return_type: Optional[FunctionNode.RetType] = None,
is_static: bool = False) -> FunctionNode:
"""Adds function as a child node of a class.
Function is classified in 3 categories:
1. Instance method.
If function is an instance method then `self` argument is
inserted at the beginning of its arguments list.
2. Class method (or factory method)
If `is_static` flag is `True` and typename of the function
return type matches name of the class then function is treated
as class method.
If function is a class method then `cls` argument is inserted
at the beginning of its arguments list.
3. Static method
Args:
name (str): Name of the function.
arguments (Sequence[FunctionNode.Arg], optional): Function arguments.
Defaults to ().
return_type (Optional[FunctionNode.RetType], optional): Function
return type. Defaults to None.
is_static (bool, optional): Flag whenever function is static or not.
Defaults to False.
Returns:
FunctionNode: created function node.
"""
arguments = list(arguments)
if return_type is not None:
is_classmethod = return_type.typename == self.name
else:
is_classmethod = False
if not is_static:
arguments.insert(0, FunctionNode.Arg("self"))
elif is_classmethod:
is_static = False
arguments.insert(0, FunctionNode.Arg("cls"))
return self._add_child(FunctionNode, name, arguments=arguments,
return_type=return_type, is_static=is_static,
is_classmethod=is_classmethod)
def add_enumeration(self, name: str) -> EnumerationNode:
return self._add_child(EnumerationNode, name)
def add_constant(self, name: str, value: str) -> ConstantNode:
return self._add_child(ConstantNode, name, value=value)
def add_base(self, base_class_node: "ClassNode") -> None:
self.bases.append(weakref.proxy(base_class_node))
def resolve_type_nodes(self, root: ASTNode) -> None:
"""Resolves type nodes for all inner-classes, methods and properties
in 2 steps:
1. Resolve against `self` as a tree root
2. Resolve against `root` as a tree root
Type resolution errors are postponed until all children nodes are
examined.
Args:
root (Optional[ASTNode], optional): Root of the AST sub-tree.
Defaults to None.
"""
errors = []
for child in itertools.chain(self.properties,
self.functions.values(),
self.classes.values()):
try:
try:
# Give priority to narrowest scope (class-level scope in this case)
child.resolve_type_nodes(self) # type: ignore
except TypeResolutionError:
child.resolve_type_nodes(root) # type: ignore
except TypeResolutionError as e:
errors.append(str(e))
if len(errors) > 0:
raise TypeResolutionError(
'Failed to resolve "{}" class against "{}". Errors: {}'.format(
self.full_export_name, root.full_export_name, errors
)
)
class ProtocolClassNode(ClassNode):
def __init__(self, name: str, parent: Optional[ASTNode] = None,
export_name: Optional[str] = None,
properties: Sequence[ClassProperty] = ()) -> None:
super().__init__(name, parent, export_name, bases=(),
properties=properties)
@@ -0,0 +1,31 @@
from typing import Optional, Tuple
from .node import ASTNode, ASTNodeType
class ConstantNode(ASTNode):
"""Represents C++ constant that is also a constant in Python.
"""
def __init__(self, name: str, value: str,
parent: Optional[ASTNode] = None,
export_name: Optional[str] = None) -> None:
super().__init__(name, parent, export_name)
self.value = value
self._value_type = "int"
@property
def children_types(self) -> Tuple[ASTNodeType, ...]:
return ()
@property
def node_type(self) -> ASTNodeType:
return ASTNodeType.Constant
@property
def value_type(self) -> str:
return self._value_type
def __str__(self) -> str:
return "Constant('{}' exported as '{}': {})".format(
self.name, self.export_name, self.value
)
@@ -0,0 +1,33 @@
from typing import Type, Tuple, Optional, Dict
from .node import ASTNode, ASTNodeType
from .constant_node import ConstantNode
class EnumerationNode(ASTNode):
"""Represents C++ enumeration that treated as named set of constants in
Python.
EnumerationNode can have only constants as its children nodes.
"""
def __init__(self, name: str, is_scoped: bool = False,
parent: Optional[ASTNode] = None,
export_name: Optional[str] = None) -> None:
super().__init__(name, parent, export_name)
self.is_scoped = is_scoped
@property
def children_types(self) -> Tuple[ASTNodeType, ...]:
return (ASTNodeType.Constant, )
@property
def node_type(self) -> ASTNodeType:
return ASTNodeType.Enumeration
@property
def constants(self) -> Dict[str, ConstantNode]:
return self._children[ASTNodeType.Constant]
def add_constant(self, name: str, value: str) -> ConstantNode:
return self._add_child(ConstantNode, name, value=value)
@@ -0,0 +1,140 @@
from typing import NamedTuple, Sequence, Optional, Tuple, List
from .node import ASTNode, ASTNodeType
from .type_node import TypeNode, NoneTypeNode, TypeResolutionError
class FunctionNode(ASTNode):
"""Represents a function (or class method) in both C++ and Python.
This class defines an overload set rather then function itself, because
function without overloads is represented as FunctionNode with 1 overload.
"""
class Arg:
def __init__(self, name: str, type_node: Optional[TypeNode] = None,
default_value: Optional[str] = None) -> None:
self.name = name
self.type_node = type_node
self.default_value = default_value
@property
def typename(self) -> Optional[str]:
return getattr(self.type_node, "full_typename", None)
def relative_typename(self, root: str) -> Optional[str]:
if self.type_node is not None:
return self.type_node.relative_typename(root)
return None
def __str__(self) -> str:
return (
f"Arg(name={self.name}, type_node={self.type_node},"
f" default_value={self.default_value})"
)
def __repr__(self) -> str:
return str(self)
class RetType:
def __init__(self, type_node: TypeNode = NoneTypeNode("void")) -> None:
self.type_node = type_node
@property
def typename(self) -> str:
return self.type_node.full_typename
def relative_typename(self, root: str) -> Optional[str]:
return self.type_node.relative_typename(root)
def __str__(self) -> str:
return f"RetType(type_node={self.type_node})"
def __repr__(self) -> str:
return str(self)
class Overload(NamedTuple):
arguments: Sequence["FunctionNode.Arg"] = ()
return_type: Optional["FunctionNode.RetType"] = None
def __init__(self, name: str,
arguments: Optional[Sequence["FunctionNode.Arg"]] = None,
return_type: Optional["FunctionNode.RetType"] = None,
is_static: bool = False,
is_classmethod: bool = False,
parent: Optional[ASTNode] = None,
export_name: Optional[str] = None) -> None:
"""Function node initializer
Args:
name (str): Name of the function overload set
arguments (Optional[Sequence[FunctionNode.Arg]], optional): Function
arguments. If this argument is None, then no overloads are
added and node should be treated like a "function stub" rather
than function. This might be helpful if there is a knowledge
that function with the defined name exists, but information
about its interface is not available at that moment.
Defaults to None.
return_type (Optional[FunctionNode.RetType], optional): Function
return type. Defaults to None.
is_static (bool, optional): Flag pointing that function is
a static method of some class. Defaults to False.
is_classmethod (bool, optional): Flag pointing that function is
a class method of some class. Defaults to False.
parent (Optional[ASTNode], optional): Parent ASTNode of the function.
Can be class or namespace. Defaults to None.
export_name (Optional[str], optional): Export name of the function.
Defaults to None.
"""
super().__init__(name, parent, export_name)
self.overloads: List[FunctionNode.Overload] = []
self.is_static = is_static
self.is_classmethod = is_classmethod
if arguments is not None:
self.add_overload(arguments, return_type)
@property
def node_type(self) -> ASTNodeType:
return ASTNodeType.Function
@property
def children_types(self) -> Tuple[ASTNodeType, ...]:
return ()
def add_overload(self, arguments: Sequence["FunctionNode.Arg"] = (),
return_type: Optional["FunctionNode.RetType"] = None):
self.overloads.append(FunctionNode.Overload(arguments, return_type))
def resolve_type_nodes(self, root: ASTNode):
"""Resolves type nodes in all overloads against `root`
Type resolution errors are postponed until all type nodes are examined.
Args:
root (ASTNode): Root of AST sub-tree used for type nodes resolution.
"""
def has_unresolved_type_node(item) -> bool:
return item.type_node is not None and not item.type_node.is_resolved
errors = []
for overload in self.overloads:
for arg in filter(has_unresolved_type_node, overload.arguments):
try:
arg.type_node.resolve(root) # type: ignore
except TypeResolutionError as e:
errors.append(
'Failed to resolve "{}" argument: {}'.format(arg.name, e)
)
if overload.return_type is not None and \
has_unresolved_type_node(overload.return_type):
try:
overload.return_type.type_node.resolve(root)
except TypeResolutionError as e:
errors.append('Failed to resolve return type: {}'.format(e))
if len(errors) > 0:
raise TypeResolutionError(
'Failed to resolve "{}" function against "{}". Errors: {}'.format(
self.full_export_name, root.full_export_name,
", ".join("[{}]: {}".format(i, e) for i, e in enumerate(errors))
)
)
@@ -0,0 +1,113 @@
import itertools
import weakref
from collections import defaultdict
from typing import Dict, List, Optional, Sequence, Tuple
from .class_node import ClassNode, ClassProperty
from .constant_node import ConstantNode
from .enumeration_node import EnumerationNode
from .function_node import FunctionNode
from .node import ASTNode, ASTNodeType
from .type_node import TypeResolutionError
class NamespaceNode(ASTNode):
"""Represents C++ namespace that treated as module in Python.
NamespaceNode can have other namespaces, classes, functions, enumerations
and global constants as its children nodes.
"""
def __init__(self, name: str, parent: Optional[ASTNode] = None,
export_name: Optional[str] = None) -> None:
super().__init__(name, parent, export_name)
self.reexported_submodules: List[str] = []
"""List of reexported submodules"""
self.reexported_submodules_symbols: Dict[str, List[str]] = defaultdict(list)
"""Mapping between submodules export names and their symbols re-exported
in this module"""
@property
def node_type(self) -> ASTNodeType:
return ASTNodeType.Namespace
@property
def children_types(self) -> Tuple[ASTNodeType, ...]:
return (ASTNodeType.Namespace, ASTNodeType.Class, ASTNodeType.Function,
ASTNodeType.Enumeration, ASTNodeType.Constant)
@property
def namespaces(self) -> Dict[str, "NamespaceNode"]:
return self._children[ASTNodeType.Namespace]
@property
def classes(self) -> Dict[str, ClassNode]:
return self._children[ASTNodeType.Class]
@property
def functions(self) -> Dict[str, FunctionNode]:
return self._children[ASTNodeType.Function]
@property
def enumerations(self) -> Dict[str, EnumerationNode]:
return self._children[ASTNodeType.Enumeration]
@property
def constants(self) -> Dict[str, ConstantNode]:
return self._children[ASTNodeType.Constant]
def add_namespace(self, name: str) -> "NamespaceNode":
return self._add_child(NamespaceNode, name)
def add_class(self, name: str,
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
properties: Sequence[ClassProperty] = ()) -> "ClassNode":
return self._add_child(ClassNode, name, bases=bases,
properties=properties)
def add_function(self, name: str, arguments: Sequence[FunctionNode.Arg] = (),
return_type: Optional[FunctionNode.RetType] = None) -> FunctionNode:
return self._add_child(FunctionNode, name, arguments=arguments,
return_type=return_type)
def add_enumeration(self, name: str) -> EnumerationNode:
return self._add_child(EnumerationNode, name)
def add_constant(self, name: str, value: str) -> ConstantNode:
return self._add_child(ConstantNode, name, value=value)
def resolve_type_nodes(self, root: Optional[ASTNode] = None) -> None:
"""Resolves type nodes for all children nodes in 2 steps:
1. Resolve against `self` as a tree root
2. Resolve against `root` as a tree root
Type resolution errors are postponed until all children nodes are
examined.
Args:
root (Optional[ASTNode], optional): Root of the AST sub-tree.
Defaults to None.
"""
errors = []
for child in itertools.chain(self.functions.values(),
self.classes.values(),
self.namespaces.values()):
try:
try:
child.resolve_type_nodes(self) # type: ignore
except TypeResolutionError:
if root is not None:
child.resolve_type_nodes(root) # type: ignore
else:
raise
except TypeResolutionError as e:
errors.append(str(e))
if len(errors) > 0:
raise TypeResolutionError(
'Failed to resolve "{}" namespace against "{}". '
'Errors: {}'.format(
self.full_export_name,
root if root is None else root.full_export_name,
errors
)
)
@@ -0,0 +1,233 @@
import abc
import enum
import itertools
from typing import (Iterator, Type, TypeVar, Dict,
Optional, Tuple, DefaultDict)
from collections import defaultdict
import weakref
ASTNodeSubtype = TypeVar("ASTNodeSubtype", bound="ASTNode")
NodeType = Type["ASTNode"]
NameToNode = Dict[str, ASTNodeSubtype]
class ASTNodeType(enum.Enum):
Namespace = enum.auto()
Class = enum.auto()
Function = enum.auto()
Enumeration = enum.auto()
Constant = enum.auto()
class ASTNode:
"""Represents an element of the Abstract Syntax Tree produced by parsing
public C++ headers.
NOTE: Every node manages a lifetime of its children nodes. Children nodes
contain only weak references to their direct parents, so there are no
circular dependencies.
"""
def __init__(self, name: str, parent: Optional["ASTNode"] = None,
export_name: Optional[str] = None) -> None:
"""ASTNode initializer
Args:
name (str): name of the node, should be unique inside enclosing
context (There can't be 2 classes with the same name defined
in the same namespace).
parent (ASTNode, optional): parent node expressing node context.
None corresponds to globally defined object e.g. root namespace
or function without namespace. Defaults to None.
export_name (str, optional): export name of the node used to resolve
issues in languages without proper overload resolution and
provide more meaningful naming. Defaults to None.
"""
FORBIDDEN_SYMBOLS = ";,*&#/|\\@!()[]^% "
for forbidden_symbol in FORBIDDEN_SYMBOLS:
assert forbidden_symbol not in name, \
"Invalid node identifier '{}' - contains 1 or more "\
"forbidden symbols: ({})".format(name, FORBIDDEN_SYMBOLS)
assert ":" not in name, \
"Name '{}' contains C++ scope symbols (':'). Convert the name to "\
"Python style and create appropriate parent nodes".format(name)
assert "." not in name, \
"Trying to create a node with '.' symbols in its name ({}). " \
"Dots are supposed to be a scope delimiters, so create all nodes in ('{}') " \
"and add '{}' as a last child node".format(
name,
"->".join(name.split('.')[:-1]),
name.rsplit('.', maxsplit=1)[-1]
)
self.__name = name
self.export_name = name if export_name is None else export_name
self._parent: Optional["ASTNode"] = None
self.parent = parent
self.is_exported = True
self._children: DefaultDict[ASTNodeType, NameToNode] = defaultdict(dict)
def __str__(self) -> str:
return "{}('{}' exported as '{}')".format(
self.node_type.name, self.name, self.export_name
)
def __repr__(self) -> str:
return str(self)
@abc.abstractproperty
def children_types(self) -> Tuple[ASTNodeType, ...]:
"""Set of ASTNode types that are allowed to be children of this node
Returns:
Tuple[ASTNodeType, ...]: Types of children nodes
"""
pass
@abc.abstractproperty
def node_type(self) -> ASTNodeType:
"""Type of the ASTNode that can be used to distinguish nodes without
importing all subclasses of ASTNode
Returns:
ASTNodeType: Current node type
"""
pass
def node_type_name(self) -> str:
return f"{self.node_type.name}::{self.name}"
@property
def name(self) -> str:
return self.__name
@property
def native_name(self) -> str:
return self.full_name.replace(".", "::")
@property
def full_name(self) -> str:
return self._construct_full_name("name")
@property
def full_export_name(self) -> str:
return self._construct_full_name("export_name")
@property
def parent(self) -> Optional["ASTNode"]:
return self._parent
@parent.setter
def parent(self, value: Optional["ASTNode"]) -> None:
assert value is None or isinstance(value, ASTNode), \
"ASTNode.parent should be None or another ASTNode, " \
"but got: {}".format(type(value))
if value is not None:
value.__check_child_before_add(self, self.name)
# Detach from previous parent
if self._parent is not None:
self._parent._children[self.node_type].pop(self.name)
if value is None:
self._parent = None
return
# Set a weak reference to a new parent and add self to its children
self._parent = weakref.proxy(value)
value._children[self.node_type][self.name] = self
def __check_child_before_add(self, child: ASTNodeSubtype,
name: str) -> None:
assert len(self.children_types) > 0, (
f"Trying to add child node '{child.node_type_name}' to node "
f"'{self.node_type_name}' that can't have children nodes"
)
assert child.node_type in self.children_types, \
"Trying to add child node '{}' to node '{}' " \
"that supports only ({}) as its children types".format(
child.node_type_name, self.node_type_name,
",".join(t.name for t in self.children_types)
)
if self._find_child(child.node_type, name) is not None:
raise ValueError(
f"Node '{self.node_type_name}' already has a "
f"child '{child.node_type_name}'"
)
def _add_child(self, child_type: Type[ASTNodeSubtype], name: str,
**kwargs) -> ASTNodeSubtype:
"""Creates a child of the node with the given type and performs common
validation checks:
- Node can have children of the provided type
- Node doesn't have child with the same name
NOTE: Shouldn't be used directly by a user.
Args:
child_type (Type[ASTNodeSubtype]): Type of the child to create.
name (str): Name of the child.
**kwargs: Extra keyword arguments supplied to child_type.__init__
method.
Returns:
ASTNodeSubtype: Created ASTNode
"""
return child_type(name, parent=self, **kwargs)
def _find_child(self, child_type: ASTNodeType,
name: str) -> Optional[ASTNodeSubtype]:
"""Looks for child node with the given type and name.
Args:
child_type (ASTNodeType): Type of the child node.
name (str): Name of the child node.
Returns:
Optional[ASTNodeSubtype]: child node if it can be found, None
otherwise.
"""
if child_type not in self._children:
return None
return self._children[child_type].get(name, None)
def _construct_full_name(self, property_name: str) -> str:
"""Traverses nodes hierarchy upright to the root node and constructs a
full name of the node using original or export names depending on the
provided `property_name` argument.
Args:
property_name (str): Name of the property to quire from node to get
its name. Should be `name` or `export_name`.
Returns:
str: full node name where each node part is divided with a dot.
"""
def get_name(node: ASTNode) -> str:
return getattr(node, property_name)
assert property_name in ('name', 'export_name'), 'Invalid name property'
name_parts = [get_name(self), ]
parent = self.parent
while parent is not None:
name_parts.append(get_name(parent))
parent = parent.parent
return ".".join(reversed(name_parts))
def __iter__(self) -> Iterator["ASTNode"]:
return iter(itertools.chain.from_iterable(
node
# Iterate over mapping between node type and nodes dict
for children_nodes in self._children.values()
# Iterate over mapping between node name and node
for node in children_nodes.values()
))
@@ -0,0 +1,991 @@
from typing import Sequence, Generator, Tuple, Optional, Union
import weakref
import abc
from itertools import chain
from .node import ASTNode, ASTNodeType
class TypeResolutionError(Exception):
pass
class TypeNode(abc.ABC):
"""This class and its derivatives used for construction parts of AST that
otherwise can't be constructed from the information provided by header
parser, because this information is either not available at that moment of
time or not available at all:
- There is no possible way to derive correspondence between C++ type
and its Python equivalent if it is not exposed from library
e.g. `cv::Rect`.
- There is no information about types visibility (see `ASTNodeTypeNode`).
"""
compatible_to_runtime_usage = False
"""Class-wide property that switches exported type names for several nodes.
Example:
>>> node = OptionalTypeNode(ASTNodeTypeNode("Size"))
>>> node.typename # TypeNode.compatible_to_runtime_usage == False
"Size | None"
>>> TypeNode.compatible_to_runtime_usage = True
>>> node.typename
"typing.Optional[Size]"
"""
def __init__(self, ctype_name: str, required_modules: Tuple[str, ...] = ()) -> None:
self.ctype_name = ctype_name
self._required_modules = required_modules
@abc.abstractproperty
def typename(self) -> str:
"""Short name of the type node used that should be used in the same
module (or a file) where type is defined.
Returns:
str: short name of the type node.
"""
return ""
@property
def full_typename(self) -> str:
"""Full name of the type node including full module name starting from
the package.
Example: 'cv2.Algorithm', 'cv2.gapi.ie.PyParams'.
Returns:
str: full name of the type node.
"""
return self.typename
@property
def required_definition_imports(self) -> Generator[str, None, None]:
"""Generator filled with import statements required for type
node definition (especially used by `AliasTypeNode`).
Example:
```python
# Alias defined in the `cv2.typing.__init__.pyi`
Callback = typing.Callable[[cv2.GMat, float], None]
# alias definition
callback_alias = AliasTypeNode.callable_(
'Callback',
arg_types=(ASTNodeTypeNode('GMat'), PrimitiveTypeNode.float_())
)
# Required definition imports
for required_import in callback_alias.required_definition_imports:
print(required_import)
# Outputs:
# 'import typing'
# 'import cv2'
```
Yields:
Generator[str, None, None]: generator filled with import statements
required for type node definition.
"""
yield from ()
@property
def required_usage_imports(self) -> Generator[str, None, None]:
"""Generator filled with import statements required for type node
usage.
Example:
```python
# Alias defined in the `cv2.typing.__init__.pyi`
Callback = typing.Callable[[cv2.GMat, float], None]
# alias definition
callback_alias = AliasTypeNode.callable_(
'Callback',
arg_types=(ASTNodeTypeNode('GMat'), PrimitiveTypeNode.float_())
)
# Required usage imports
for required_import in callback_alias.required_usage_imports:
print(required_import)
# Outputs:
# 'import cv2.typing'
```
Yields:
Generator[str, None, None]: generator filled with import statements
required for type node definition.
"""
yield from ()
@property
def required_modules(self) -> Tuple[str, ...]:
return self._required_modules
@property
def is_resolved(self) -> bool:
return True
def relative_typename(self, module: str) -> str:
"""Type name relative to the provided module.
Args:
module (str): Full export name of the module to get relative name to.
Returns:
str: If module name of the type node doesn't match `module`, then
returns class scopes + `self.typename`, otherwise
`self.full_typename`.
"""
return self.full_typename
def resolve(self, root: ASTNode) -> None:
"""Resolves all references to AST nodes using a top-down search
for nodes with corresponding export names. See `_resolve_symbol` for
more details.
Args:
root (ASTNode): Node pointing to the root of a subtree in AST
representing search scope of the symbol.
Most of the symbols don't have full paths in their names, so
scopes should be examined in bottom-up manner starting
with narrowest one.
Raises:
TypeResolutionError: if at least 1 reference to AST node can't
be resolved in the subtree pointed by the root.
"""
pass
class NoneTypeNode(TypeNode):
"""Type node representing a None (or `void` in C++) type.
"""
@property
def typename(self) -> str:
return "None"
class AnyTypeNode(TypeNode):
"""Type node representing any type (most of the time it means unknown).
"""
@property
def typename(self) -> str:
return "_typing.Any"
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import typing as _typing"
class PrimitiveTypeNode(TypeNode):
"""Type node representing a primitive built-in types e.g. int, float, str.
"""
def __init__(self, ctype_name: str,
typename: Optional[str] = None,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, required_modules)
self._typename = typename if typename is not None else ctype_name
@property
def typename(self) -> str:
return self._typename
@classmethod
def int_(cls, ctype_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
if ctype_name is None:
ctype_name = "int"
return PrimitiveTypeNode(ctype_name, typename="int", required_modules=required_modules)
@classmethod
def float_(cls, ctype_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
if ctype_name is None:
ctype_name = "float"
return PrimitiveTypeNode(ctype_name, typename="float", required_modules=required_modules)
@classmethod
def bool_(cls, ctype_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
if ctype_name is None:
ctype_name = "bool"
return PrimitiveTypeNode(ctype_name, typename="bool", required_modules=required_modules)
@classmethod
def str_(cls, ctype_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
if ctype_name is None:
ctype_name = "string"
return PrimitiveTypeNode(ctype_name, "str", required_modules=required_modules)
class AliasRefTypeNode(TypeNode):
"""Type node representing an alias referencing another alias. Example:
```python
Point2i = tuple[int, int]
Point = Point2i
```
During typing stubs generation procedure above code section might be defined
as follows
```python
AliasTypeNode.tuple_("Point2i",
items=(
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.int_()
))
AliasTypeNode.ref_("Point", "Point2i")
```
"""
def __init__(self, alias_ctype_name: str,
alias_export_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
super().__init__(alias_ctype_name, required_modules)
if alias_export_name is None:
self.alias_export_name = alias_ctype_name
else:
self.alias_export_name = alias_export_name
@property
def typename(self) -> str:
return self.alias_export_name
@property
def full_typename(self) -> str:
return "cv2.typing." + self.typename
class AliasTypeNode(TypeNode):
"""Type node representing an alias to another type.
Example:
```python
Point2i = tuple[int, int]
```
can be defined as
```python
AliasTypeNode.tuple_("Point2i",
items=(
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.int_()
))
```
Under the hood it is implemented as a container of another type node.
"""
def __init__(self, ctype_name: str, value: TypeNode,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, required_modules)
self.value = value
# If alias is exported as is - use its ctype_name
if export_name is None:
forbidden_symbols = (":", "*", "&")
assert all(symbol not in ctype_name for symbol in forbidden_symbols), (
"Failed to create AliasTypeNode without export_name. "
f"'{ctype_name}' should not contain any of {forbidden_symbols}"
)
self._export_name = ctype_name
else:
self._export_name = export_name
self.doc = doc
@property
def typename(self) -> str:
return self._export_name
@property
def full_typename(self) -> str:
return "cv2.typing." + self.typename
@property
def required_definition_imports(self) -> Generator[str, None, None]:
return self.value.required_usage_imports
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import cv2.typing"
@property
def is_resolved(self) -> bool:
return self.value.is_resolved
def resolve(self, root: ASTNode):
try:
self.value.resolve(root)
except TypeResolutionError as e:
raise TypeResolutionError(
'Failed to resolve alias "{}" exposed as "{}"'.format(
self.ctype_name, self.typename
)
) from e
@classmethod
def int_(cls, ctype_name: str, export_name: Optional[str] = None,
doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc, required_modules)
@classmethod
def float_(cls, ctype_name: str, export_name: Optional[str] = None,
doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc, required_modules)
@classmethod
def array_ref_(cls, ctype_name: str, array_ref_name: str,
shape: Optional[Tuple[int, ...]],
dtype: Optional[str] = None,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
"""Create alias to array reference alias `array_ref_name`.
This is required to preserve backward compatibility with Python < 3.9
and NumPy 1.20, when NumPy module introduces generics support.
Args:
ctype_name (str): Name of the alias.
array_ref_name (str): Name of the conditional array alias.
shape (Optional[Tuple[int, ...]]): Array shape.
dtype (Optional[str], optional): Array type. Defaults to None.
export_name (Optional[str], optional): Alias export name.
Defaults to None.
doc (Optional[str], optional): Documentation string for alias.
Defaults to None.
"""
if doc is None:
doc = f"NDArray(shape={shape}, dtype={dtype})"
else:
doc += f". NDArray(shape={shape}, dtype={dtype})"
return cls(ctype_name, AliasRefTypeNode(array_ref_name),
export_name, doc, required_modules)
@classmethod
def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, UnionTypeNode(ctype_name, items),
export_name, doc, required_modules)
@classmethod
def optional_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, OptionalTypeNode(item), export_name, doc, required_modules)
@classmethod
def sequence_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, SequenceTypeNode(ctype_name, item),
export_name, doc, required_modules)
@classmethod
def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, TupleTypeNode(ctype_name, items),
export_name, doc, required_modules)
@classmethod
def class_(cls, ctype_name: str, class_name: str,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, ASTNodeTypeNode(class_name),
export_name, doc, required_modules)
@classmethod
def callable_(cls, ctype_name: str,
arg_types: Union[TypeNode, Sequence[TypeNode]],
ret_type: TypeNode = NoneTypeNode("void"),
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name,
CallableTypeNode(ctype_name, arg_types, ret_type),
export_name, doc, required_modules)
@classmethod
def ref_(cls, ctype_name: str, alias_ctype_name: str,
alias_export_name: Optional[str] = None,
export_name: Optional[str] = None,
doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name,
AliasRefTypeNode(alias_ctype_name, alias_export_name),
export_name, doc, required_modules)
@classmethod
def dict_(cls, ctype_name: str, key_type: TypeNode, value_type: TypeNode,
export_name: Optional[str] = None, doc: Optional[str] = None,
required_modules: Tuple[str, ...] = ()):
return cls(ctype_name, DictTypeNode(ctype_name, key_type, value_type),
export_name, doc, required_modules)
class ConditionalAliasTypeNode(TypeNode):
"""Type node representing an alias protected by condition checked in runtime.
For typing-related conditions, prefer using typing.TYPE_CHECKING. For a full explanation, see:
https://github.com/opencv/opencv/pull/23927#discussion_r1256326835
Example:
```python
if typing.TYPE_CHECKING
NumPyArray = numpy.ndarray[typing.Any, numpy.dtype[numpy.generic]]
else:
NumPyArray = numpy.ndarray
```
is defined as follows:
```python
ConditionalAliasTypeNode(
"NumPyArray",
'typing.TYPE_CHECKING',
NDArrayTypeNode("NumPyArray"),
NDArrayTypeNode("NumPyArray", use_numpy_generics=False),
condition_required_imports=("import typing",)
)
```
"""
def __init__(self, ctype_name: str, condition: str,
positive_branch_type: TypeNode,
negative_branch_type: TypeNode,
export_name: Optional[str] = None,
condition_required_imports: Sequence[str] = ()) -> None:
super().__init__(ctype_name)
self.condition = condition
self.positive_branch_type = positive_branch_type
self.positive_branch_type.ctype_name = self.ctype_name
self.negative_branch_type = negative_branch_type
self.negative_branch_type.ctype_name = self.ctype_name
self._export_name = export_name
self._condition_required_imports = condition_required_imports
@property
def typename(self) -> str:
if self._export_name is not None:
return self._export_name
return self.ctype_name
@property
def full_typename(self) -> str:
return "cv2.typing." + self.typename
@property
def required_definition_imports(self) -> Generator[str, None, None]:
yield from self.positive_branch_type.required_usage_imports
yield from self.negative_branch_type.required_usage_imports
yield from self._condition_required_imports
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import cv2.typing"
@property
def required_modules(self) -> Tuple[str, ...]:
return (*self.positive_branch_type.required_modules,
*self.negative_branch_type.required_modules)
@property
def is_resolved(self) -> bool:
return self.positive_branch_type.is_resolved \
and self.negative_branch_type.is_resolved
def resolve(self, root: ASTNode):
try:
self.positive_branch_type.resolve(root)
self.negative_branch_type.resolve(root)
except TypeResolutionError as e:
raise TypeResolutionError(
'Failed to resolve alias "{}" exposed as "{}"'.format(
self.ctype_name, self.typename
)
) from e
@classmethod
def numpy_array_(cls, ctype_name: str, export_name: Optional[str] = None,
shape: Optional[Tuple[int, ...]] = None,
dtype: Optional[str] = None):
"""Type subscription is not possible in python 3.8 and older numpy versions."""
return cls(
ctype_name,
"_typing.TYPE_CHECKING",
NDArrayTypeNode(ctype_name, shape, dtype),
NDArrayTypeNode(ctype_name, shape, dtype,
use_numpy_generics=False),
condition_required_imports=("import typing as _typing",)
)
class NDArrayTypeNode(TypeNode):
"""Type node representing NumPy ndarray.
"""
def __init__(self, ctype_name: str,
shape: Optional[Tuple[int, ...]] = None,
dtype: Optional[str] = None,
use_numpy_generics: bool = True) -> None:
super().__init__(ctype_name)
self.shape = shape
self.dtype = dtype
self._use_numpy_generics = use_numpy_generics
@property
def typename(self) -> str:
if self._use_numpy_generics:
# NOTE: Shape is not fully supported yet
dtype = self.dtype if self.dtype is not None else "numpy.generic"
return f"numpy.ndarray[_typing.Any, numpy.dtype[{dtype}]]"
return "numpy.ndarray"
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import numpy"
# if self.shape is None:
yield "import typing as _typing"
class ASTNodeTypeNode(TypeNode):
"""Type node representing a lazy ASTNode corresponding to type of
function argument or its return type or type of class property.
Introduced laziness nature resolves the types visibility issue - all types
should be known during function declaration to select an appropriate node
from the AST. Such knowledge leads to evaluation of all preprocessor
directives (`#include` particularly) for each processed header and might be
too expensive and error prone.
"""
def __init__(self, ctype_name: str, typename: Optional[str] = None,
module_name: Optional[str] = None,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, required_modules)
self._typename = typename if typename is not None else ctype_name
self._module_name = module_name
self._ast_node: Optional[weakref.ProxyType[ASTNode]] = None
@property
def ast_node(self):
return self._ast_node
@property
def typename(self) -> str:
if self._ast_node is None:
return self._typename
typename = self._ast_node.export_name
if self._ast_node.node_type is not ASTNodeType.Enumeration:
return typename
# NOTE: Special handling for enums
parent = self._ast_node.parent
while parent.node_type is ASTNodeType.Class:
typename = parent.export_name + "_" + typename
parent = parent.parent
return typename
@property
def full_typename(self) -> str:
if self._ast_node is not None:
if self._ast_node.node_type is not ASTNodeType.Enumeration:
return self._ast_node.full_export_name
# NOTE: enumerations are exported to module scope
typename = self._ast_node.export_name
parent = self._ast_node.parent
while parent.node_type is ASTNodeType.Class:
typename = parent.export_name + "_" + typename
parent = parent.parent
return parent.full_export_name + "." + typename
if self._module_name is not None:
return self._module_name + "." + self._typename
return self._typename
@property
def required_usage_imports(self) -> Generator[str, None, None]:
if self._module_name is None:
assert self._ast_node is not None, \
"Can't find a module for class '{}' exported as '{}'".format(
self.ctype_name, self.typename,
)
module = self._ast_node.parent
while module.node_type is not ASTNodeType.Namespace:
module = module.parent
yield "import " + module.full_export_name
else:
yield "import " + self._module_name
@property
def is_resolved(self) -> bool:
return self._ast_node is not None or self._module_name is not None
def resolve(self, root: ASTNode):
if self.is_resolved:
return
node = _resolve_symbol(root, self.typename)
if node is None:
raise TypeResolutionError('Failed to resolve "{}" exposed as "{}"'.format(
self.ctype_name, self.typename
))
self._ast_node = weakref.proxy(node)
def relative_typename(self, module: str) -> str:
assert self._ast_node is not None or self._module_name is not None, \
"'{}' exported as '{}' is not resolved yet".format(self.ctype_name,
self.typename)
if self._module_name is None:
type_module = self._ast_node.parent # type: ignore
while type_module.node_type is not ASTNodeType.Namespace:
type_module = type_module.parent
module_name = type_module.full_export_name
else:
module_name = self._module_name
if module_name != module:
return self.full_typename
return self.full_typename[len(module_name) + 1:]
class AggregatedTypeNode(TypeNode):
"""Base type node for type nodes representing an aggregation of another
type nodes e.g. tuple, sequence or callable."""
def __init__(self, ctype_name: str, items: Sequence[TypeNode],
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, required_modules)
self.items = list(items)
@property
def is_resolved(self) -> bool:
return all(item.is_resolved for item in self.items)
@property
def required_modules(self) -> Tuple[str, ...]:
return (*chain.from_iterable(item.required_modules for item in self.items),
*self._required_modules)
def resolve(self, root: ASTNode) -> None:
errors = []
for item in filter(lambda item: not item.is_resolved, self):
try:
item.resolve(root)
except TypeResolutionError as e:
errors.append(str(e))
if len(errors) > 0:
raise TypeResolutionError(
'Failed to resolve one of "{}" items. Errors: {}'.format(
self.full_typename, errors
)
)
def __iter__(self):
return iter(self.items)
def __len__(self) -> int:
return len(self.items)
@property
def required_definition_imports(self) -> Generator[str, None, None]:
for item in self:
yield from item.required_definition_imports
@property
def required_usage_imports(self) -> Generator[str, None, None]:
for item in self:
yield from item.required_usage_imports
class ContainerTypeNode(AggregatedTypeNode):
"""Base type node for all type nodes representing a container type.
"""
@property
def typename(self) -> str:
return self.type_format.format(self.types_separator.join(
item.typename for item in self
))
@property
def full_typename(self) -> str:
return self.type_format.format(self.types_separator.join(
item.full_typename for item in self
))
def relative_typename(self, module: str) -> str:
return self.type_format.format(self.types_separator.join(
item.relative_typename(module) for item in self
))
@property
def required_definition_imports(self) -> Generator[str, None, None]:
yield "import typing as _typing"
yield from super().required_definition_imports
@property
def required_usage_imports(self) -> Generator[str, None, None]:
if TypeNode.compatible_to_runtime_usage:
yield "import typing as _typing"
yield from super().required_usage_imports
@abc.abstractproperty
def type_format(self) -> str:
return ""
@abc.abstractproperty
def types_separator(self) -> str:
return ""
class SequenceTypeNode(ContainerTypeNode):
"""Type node representing a homogeneous collection of elements with
possible unknown length.
"""
def __init__(self, ctype_name: str, item: TypeNode,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, (item, ), required_modules)
@property
def type_format(self) -> str:
return "_typing.Sequence[{}]"
@property
def types_separator(self) -> str:
return ", "
class TupleTypeNode(ContainerTypeNode):
"""Type node representing possibly heterogeneous collection of types with
possibly unspecified length.
"""
@property
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "_typing.Tuple[{}]"
return "tuple[{}]"
@property
def types_separator(self) -> str:
return ", "
class UnionTypeNode(ContainerTypeNode):
"""Type node representing type that can be one of the predefined set of types.
"""
@property
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "_typing.Union[{}]"
return "{}"
@property
def types_separator(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return ", "
return " | "
class OptionalTypeNode(ContainerTypeNode):
"""Type node representing optional type which is effectively is a union
of value type node and None.
"""
def __init__(self, value: TypeNode,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(value.ctype_name, (value,), required_modules)
@property
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "_typing.Optional[{}]"
return "{} | None"
@property
def types_separator(self) -> str:
return ", "
class DictTypeNode(ContainerTypeNode):
"""Type node representing a homogeneous key-value mapping.
"""
def __init__(self, ctype_name: str, key_type: TypeNode,
value_type: TypeNode,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(ctype_name, (key_type, value_type), required_modules)
@property
def key_type(self) -> TypeNode:
return self.items[0]
@property
def value_type(self) -> TypeNode:
return self.items[1]
@property
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "_typing.Dict[{}]"
return "dict[{}]"
@property
def types_separator(self) -> str:
return ", "
class CallableTypeNode(AggregatedTypeNode):
"""Type node representing a callable type (most probably a function).
```python
CallableTypeNode(
'image_reading_callback',
arg_types=(ASTNodeTypeNode('Image'), PrimitiveTypeNode.float_())
)
```
defines a callable type node representing a function with the same
interface as the following
```python
def image_reading_callback(image: Image, timestamp: float) -> None: ...
```
"""
def __init__(self, ctype_name: str,
arg_types: Union[TypeNode, Sequence[TypeNode]],
ret_type: TypeNode = NoneTypeNode("void"),
required_modules: Tuple[str, ...] = ()) -> None:
if isinstance(arg_types, TypeNode):
super().__init__(ctype_name, (arg_types, ret_type), required_modules)
else:
super().__init__(ctype_name, (*arg_types, ret_type), required_modules)
@property
def arg_types(self) -> Sequence[TypeNode]:
return self.items[:-1]
@property
def ret_type(self) -> TypeNode:
return self.items[-1]
@property
def typename(self) -> str:
return '_typing.Callable[[{}], {}]'.format(
', '.join(arg.typename for arg in self.arg_types),
self.ret_type.typename
)
@property
def full_typename(self) -> str:
return '_typing.Callable[[{}], {}]'.format(
', '.join(arg.full_typename for arg in self.arg_types),
self.ret_type.full_typename
)
def relative_typename(self, module: str) -> str:
return '_typing.Callable[[{}], {}]'.format(
', '.join(arg.relative_typename(module) for arg in self.arg_types),
self.ret_type.relative_typename(module)
)
@property
def required_definition_imports(self) -> Generator[str, None, None]:
yield "import typing as _typing"
yield from super().required_definition_imports
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import typing as _typing"
yield from super().required_usage_imports
class ClassTypeNode(ContainerTypeNode):
"""Type node representing types themselves (refer to typing.Type)
"""
def __init__(self, value: TypeNode,
required_modules: Tuple[str, ...] = ()) -> None:
super().__init__(value.ctype_name, (value,), required_modules)
@property
def type_format(self) -> str:
return "_typing.Type[{}]"
@property
def types_separator(self) -> str:
return ", "
class PathLikeTypeNode(TypeNode):
"""Type node representing a PathLike object.
"""
def __init__(self, ctype_name: str) -> None:
super().__init__(ctype_name)
@property
def typename(self) -> str:
return "os.PathLike[str]"
@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import os"
@staticmethod
def string_or_pathlike_(ctype_name: str = "string") -> UnionTypeNode:
return UnionTypeNode(
ctype_name,
items=(
PrimitiveTypeNode.str_(ctype_name),
PathLikeTypeNode(ctype_name)
)
)
def _resolve_symbol(root: Optional[ASTNode], full_symbol_name: str) -> Optional[ASTNode]:
"""Searches for a symbol with the given full export name in the AST
starting from the `root`.
Args:
root (Optional[ASTNode]): Root of the examining AST.
full_symbol_name (str): Full export name of the symbol to find. Path
components can be divided by '.' or '_'.
Returns:
Optional[ASTNode]: ASTNode with full export name equal to
`full_symbol_name`, None otherwise.
>>> root = NamespaceNode('cv')
>>> cls = root.add_class('Algorithm').add_class('Params')
>>> _resolve_symbol(root, 'cv.Algorithm.Params') == cls
True
>>> root = NamespaceNode('cv')
>>> enum = root.add_namespace('detail').add_enumeration('AlgorithmType')
>>> _resolve_symbol(root, 'cv_detail_AlgorithmType') == enum
True
>>> root = NamespaceNode('cv')
>>> _resolve_symbol(root, 'cv.detail.Algorithm')
None
>>> root = NamespaceNode('cv')
>>> enum = root.add_namespace('detail').add_enumeration('AlgorithmType')
>>> _resolve_symbol(root, 'AlgorithmType')
None
"""
def search_down_symbol(scope: Optional[ASTNode],
scope_sep: str) -> Optional[ASTNode]:
parts = full_symbol_name.split(scope_sep, maxsplit=1)
while len(parts) == 2:
# Try to find narrow scope
scope = _resolve_symbol(scope, parts[0])
if scope is None:
return None
# and resolve symbol in it
node = _resolve_symbol(scope, parts[1])
if node is not None:
return node
# symbol is not found, but narrowed scope is valid - diving further
parts = parts[1].split(scope_sep, maxsplit=1)
return None
assert root is not None, \
"Can't resolve symbol '{}' from NONE root".format(full_symbol_name)
# Looking for exact symbol match
for attr in filter(lambda attr: hasattr(root, attr),
("namespaces", "classes", "enumerations")):
nodes_dict = getattr(root, attr)
node = nodes_dict.get(full_symbol_name, None)
if node is not None:
return node
# Symbol is not found, looking for more fine-grained scope if possible
for scope_sep in ("_", "."):
node = search_down_symbol(root, scope_sep)
if node is not None:
return node
return None
@@ -0,0 +1,273 @@
from .nodes.type_node import (
AliasTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
ASTNodeTypeNode, NDArrayTypeNode, NoneTypeNode, SequenceTypeNode,
TupleTypeNode, UnionTypeNode, AnyTypeNode, ConditionalAliasTypeNode
)
# Set of predefined types used to cover cases when library doesn't
# directly exports a type and equivalent one should be used instead.
# Example: Instead of C++ `cv::Rect(1, 1, 5, 6)` in Python any sequence type
# with length 4 can be used: tuple `(1, 1, 5, 6)` or list `[1, 1, 5, 6]`.
# Predefined type might be:
# - alias - defines a Python synonym for a native type name.
# Example: `cv::Rect` and `cv::Size` are both `Sequence[int]` in Python, but
# with different length constraints (4 and 2 accordingly).
# - direct substitution - just a plain type replacement without any credits to
# native type. Example:
# * `std::vector<uchar>` is `np.ndarray` with `dtype == np.uint8` in Python
# * `double` is a Python `float`
# * `std::string` is a Python `str`
_PREDEFINED_TYPES = (
PrimitiveTypeNode.int_("int"),
PrimitiveTypeNode.int_("uchar"),
PrimitiveTypeNode.int_("unsigned"),
PrimitiveTypeNode.int_("int64"),
PrimitiveTypeNode.int_("uint8_t"),
PrimitiveTypeNode.int_("int8_t"),
PrimitiveTypeNode.int_("int32_t"),
PrimitiveTypeNode.int_("uint32_t"),
PrimitiveTypeNode.int_("size_t"),
PrimitiveTypeNode.int_("int64_t"),
PrimitiveTypeNode.int_("long long"),
PrimitiveTypeNode.float_("float"),
PrimitiveTypeNode.float_("double"),
PrimitiveTypeNode.bool_("bool"),
PrimitiveTypeNode.str_("string"),
PrimitiveTypeNode.str_("char"),
PrimitiveTypeNode.str_("String"),
PrimitiveTypeNode.str_("c_string"),
ConditionalAliasTypeNode.numpy_array_(
"NumPyArrayNumeric",
dtype="numpy.integer[_typing.Any] | numpy.floating[_typing.Any]"
),
ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat32", dtype="numpy.float32"),
ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat64", dtype="numpy.float64"),
NoneTypeNode("void"),
AliasTypeNode.int_("void*", "IntPointer", "Represents an arbitrary pointer"),
AliasTypeNode.union_(
"Mat",
items=(ASTNodeTypeNode("Mat", module_name="cv2.mat_wrapper"),
AliasRefTypeNode("NumPyArrayNumeric")),
export_name="MatLike"
),
AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()),
AliasTypeNode.sequence_("Size", PrimitiveTypeNode.int_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Size2f", PrimitiveTypeNode.float_(),
doc="Required length is 2"),
AliasTypeNode.union_(
"Scalar",
items=(SequenceTypeNode("Scalar", PrimitiveTypeNode.float_()),
PrimitiveTypeNode.float_()),
doc="Max sequence length is at most 4"
),
AliasTypeNode.sequence_("Point", PrimitiveTypeNode.int_(),
doc="Required length is 2"),
AliasTypeNode.ref_("Point2i", "Point"),
AliasTypeNode.sequence_("Point2f", PrimitiveTypeNode.float_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Point2d", PrimitiveTypeNode.float_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Point3i", PrimitiveTypeNode.int_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Point3f", PrimitiveTypeNode.float_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Point3d", PrimitiveTypeNode.float_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Range", PrimitiveTypeNode.int_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Rect", PrimitiveTypeNode.int_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2i", PrimitiveTypeNode.int_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2f", PrimitiveTypeNode.float_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2d", PrimitiveTypeNode.float_(),
doc="Required length is 4"),
AliasTypeNode.dict_("Moments", PrimitiveTypeNode.str_("Moments::key"),
PrimitiveTypeNode.float_("Moments::value")),
AliasTypeNode.tuple_("RotatedRect",
items=(AliasRefTypeNode("Point2f"),
AliasRefTypeNode("Size2f"),
PrimitiveTypeNode.float_()),
doc="Any type providing sequence protocol is supported"),
AliasTypeNode.tuple_("TermCriteria",
items=(
ASTNodeTypeNode("TermCriteria.Type"),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_()),
doc="Any type providing sequence protocol is supported"),
AliasTypeNode.sequence_("Vec2i", PrimitiveTypeNode.int_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2f", PrimitiveTypeNode.float_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2d", PrimitiveTypeNode.float_(),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec3i", PrimitiveTypeNode.int_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3f", PrimitiveTypeNode.float_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3d", PrimitiveTypeNode.float_(),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec4i", PrimitiveTypeNode.int_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4f", PrimitiveTypeNode.float_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4d", PrimitiveTypeNode.float_(),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec6f", PrimitiveTypeNode.float_(),
doc="Required length is 6"),
AliasTypeNode.class_("FeatureDetector", "Feature2D",
export_name="FeatureDetector"),
AliasTypeNode.class_("DescriptorExtractor", "Feature2D",
export_name="DescriptorExtractor"),
AliasTypeNode.class_("FeatureExtractor", "Feature2D",
export_name="FeatureExtractor"),
AliasTypeNode.array_ref_("Matx33f",
array_ref_name="NumPyArrayFloat32",
shape=(3, 3),
dtype="numpy.float32"),
AliasTypeNode.array_ref_("Matx33d",
array_ref_name="NumPyArrayFloat64",
shape=(3, 3),
dtype="numpy.float64"),
AliasTypeNode.array_ref_("Matx44f",
array_ref_name="NumPyArrayFloat32",
shape=(4, 4),
dtype="numpy.float32"),
AliasTypeNode.array_ref_("Matx44d",
array_ref_name="NumPyArrayFloat64",
shape=(4, 4),
dtype="numpy.float64"),
NDArrayTypeNode("vector<uchar>", dtype="numpy.uint8"),
NDArrayTypeNode("vector_uchar", dtype="numpy.uint8"),
# DNN, optional
AliasTypeNode.class_("LayerId", "DictValue", required_modules=("dnn",)),
AliasTypeNode.dict_("LayerParams",
key_type=PrimitiveTypeNode.str_(),
value_type=UnionTypeNode("DictValue", items=(
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_(),
PrimitiveTypeNode.str_())
),
required_modules=("dnn",)),
# Flann, optional
PrimitiveTypeNode.int_("cvflann_flann_distance_t", required_modules=("flann",)),
PrimitiveTypeNode.int_("flann_flann_distance_t", required_modules=("flann",)),
PrimitiveTypeNode.int_("cvflann_flann_algorithm_t", required_modules=("flann",)),
PrimitiveTypeNode.int_("flann_flann_algorithm_t", required_modules=("flann",)),
AliasTypeNode.dict_("flann_IndexParams",
key_type=PrimitiveTypeNode.str_(),
value_type=UnionTypeNode("flann_IndexParams::value", items=(
PrimitiveTypeNode.bool_(),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_(),
PrimitiveTypeNode.str_())
),
export_name="IndexParams",
required_modules=("flann",)),
AliasTypeNode.dict_("flann_SearchParams",
key_type=PrimitiveTypeNode.str_(),
value_type=UnionTypeNode("flann_IndexParams::value", items=(
PrimitiveTypeNode.bool_(),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_(),
PrimitiveTypeNode.str_())
),
export_name="SearchParams",
required_modules=("flann",)),
AliasTypeNode.dict_("map_string_and_string",
PrimitiveTypeNode.str_("map_string_and_string::key"),
PrimitiveTypeNode.str_("map_string_and_string::value"),
required_modules=("flann",)),
AliasTypeNode.dict_("map_string_and_int",
PrimitiveTypeNode.str_("map_string_and_int::key"),
PrimitiveTypeNode.int_("map_string_and_int::value"),
required_modules=("flann",)),
AliasTypeNode.dict_("map_string_and_vector_size_t",
PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"),
SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t")),
required_modules=("flann",)),
AliasTypeNode.dict_("map_string_and_vector_float",
PrimitiveTypeNode.str_("map_string_and_vector_float::key"),
SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_()),
required_modules=("flann",)),
AliasTypeNode.dict_("map_int_and_double",
PrimitiveTypeNode.int_("map_int_and_double::key"),
PrimitiveTypeNode.float_("map_int_and_double::value"),
required_modules=("flann",)),
# G-API, optional
AliasTypeNode.union_("GProtoArg",
items=(AliasRefTypeNode("Scalar"),
ASTNodeTypeNode("GMat"),
ASTNodeTypeNode("GOpaqueT"),
ASTNodeTypeNode("GArrayT")),
required_modules=("gapi",)),
SequenceTypeNode("GProtoArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
AliasTypeNode.sequence_("GProtoInputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
AliasTypeNode.sequence_("GProtoOutputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
AliasTypeNode.union_(
"GRunArg",
items=(AliasRefTypeNode("Mat", "MatLike"),
AliasRefTypeNode("Scalar"),
ASTNodeTypeNode("GOpaqueT"),
ASTNodeTypeNode("GArrayT"),
SequenceTypeNode("GRunArg", AnyTypeNode("GRunArg")),
NoneTypeNode("GRunArg")),
required_modules=("gapi",)
),
AliasTypeNode.optional_("GOptRunArg", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)),
AliasTypeNode.union_("GMetaArg",
items=(ASTNodeTypeNode("GMat"),
AliasRefTypeNode("Scalar"),
ASTNodeTypeNode("GOpaqueT"),
ASTNodeTypeNode("GArrayT")),
required_modules=("gapi",)),
AliasTypeNode.union_("Prim",
items=(ASTNodeTypeNode("gapi.wip.draw.Text"),
ASTNodeTypeNode("gapi.wip.draw.Circle"),
ASTNodeTypeNode("gapi.wip.draw.Image"),
ASTNodeTypeNode("gapi.wip.draw.Line"),
ASTNodeTypeNode("gapi.wip.draw.Rect"),
ASTNodeTypeNode("gapi.wip.draw.Mosaic"),
ASTNodeTypeNode("gapi.wip.draw.Poly")),
required_modules=("gapi",)),
SequenceTypeNode("Prims", AliasRefTypeNode("Prim"), required_modules=("gapi",)),
TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"),
ASTNodeTypeNode("GMat")), required_modules=("gapi",)),
ASTNodeTypeNode("GOpaque", "GOpaqueT", required_modules=("gapi",)),
ASTNodeTypeNode("GArray", "GArrayT", required_modules=("gapi",)),
AliasTypeNode.union_("GTypeInfo",
items=(ASTNodeTypeNode("GMat"),
AliasRefTypeNode("Scalar"),
ASTNodeTypeNode("GOpaqueT"),
ASTNodeTypeNode("GArrayT")),
required_modules=("gapi",)),
SequenceTypeNode("GCompileArgs", ASTNodeTypeNode("GCompileArg"), required_modules=("gapi",)),
SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo"), required_modules=("gapi",)),
SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)),
SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg"), required_modules=("gapi",)),
SequenceTypeNode("GOptRunArgs", AliasRefTypeNode("GOptRunArg"), required_modules=("gapi",)),
AliasTypeNode.callable_(
"detail_ExtractArgsCallback",
arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")),
ret_type=SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg")),
export_name="ExtractArgsCallback",
required_modules=("gapi",)
),
AliasTypeNode.callable_(
"detail_ExtractMetaCallback",
arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")),
ret_type=SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg")),
export_name="ExtractMetaCallback",
required_modules=("gapi",)
),
PrimitiveTypeNode("NativeByteArray", "bytes"),
)
PREDEFINED_TYPES = dict(
zip((t.ctype_name for t in _PREDEFINED_TYPES), _PREDEFINED_TYPES)
)
@@ -0,0 +1,333 @@
from typing import Tuple, List, Optional
from .predefined_types import PREDEFINED_TYPES
from .nodes.type_node import (
TypeNode, UnionTypeNode, SequenceTypeNode, ASTNodeTypeNode, TupleTypeNode
)
def replace_template_parameters_with_placeholders(string: str) \
-> Tuple[str, Tuple[str, ...]]:
"""Replaces template parameters with `format` placeholders for all template
instantiations in provided string.
Only outermost template parameters are replaced.
Args:
string (str): input string containing C++ template instantiations
Returns:
tuple[str, tuple[str, ...]]: string with '{}' placeholders template
instead of instantiation types and a tuple of extracted types.
>>> template_string, args = replace_template_parameters_with_placeholders(
... "std::vector<cv::Point<int>>, test<int>"
... )
>>> template_string.format(*args) == "std::vector<cv::Point<int>>, test<int>"
True
>>> replace_template_parameters_with_placeholders(
... "cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>"
... )
('cv::util::variant<{}>', ('cv::GRunArgs, cv::GOptRunArgs',))
>>> replace_template_parameters_with_placeholders("vector<Point<int>>")
('vector<{}>', ('Point<int>',))
>>> replace_template_parameters_with_placeholders(
... "vector<Point<int>>, vector<float>"
... )
('vector<{}>, vector<{}>', ('Point<int>', 'float'))
>>> replace_template_parameters_with_placeholders("string without templates")
('string without templates', ())
"""
template_brackets_indices = []
template_instantiations_count = 0
template_start_index = 0
for i, c in enumerate(string):
if c == "<":
template_instantiations_count += 1
if template_instantiations_count == 1:
# + 1 - because left bound is included in substring range
template_start_index = i + 1
elif c == ">":
template_instantiations_count -= 1
assert template_instantiations_count >= 0, \
"Provided string is ill-formed. There are more '>' than '<'."
if template_instantiations_count == 0:
template_brackets_indices.append((template_start_index, i))
assert template_instantiations_count == 0, \
"Provided string is ill-formed. There are more '<' than '>'."
template_args: List[str] = []
# Reversed loop is required to preserve template start/end indices
for i, j in reversed(template_brackets_indices):
template_args.insert(0, string[i:j])
string = string[:i] + "{}" + string[j:]
return string, tuple(template_args)
def get_template_instantiation_type(typename: str) -> str:
"""Extracts outermost template instantiation type from provided string
Args:
typename (str): String containing C++ template instantiation.
Returns:
str: String containing template instantiation type
>>> get_template_instantiation_type("std::vector<cv::Point<int>>")
'cv::Point<int>'
>>> get_template_instantiation_type("std::vector<uchar>")
'uchar'
>>> get_template_instantiation_type("std::map<int, float>")
'int, float'
>>> get_template_instantiation_type("uchar")
Traceback (most recent call last):
...
ValueError: typename ('uchar') doesn't contain template instantiations
>>> get_template_instantiation_type("std::vector<int>, std::vector<float>")
Traceback (most recent call last):
...
ValueError: typename ('std::vector<int>, std::vector<float>') contains more than 1 template instantiation
"""
_, args = replace_template_parameters_with_placeholders(typename)
if len(args) == 0:
raise ValueError(
"typename ('{}') doesn't contain template instantiations".format(typename)
)
if len(args) > 1:
raise ValueError(
"typename ('{}') contains more than 1 template instantiation".format(typename)
)
return args[0]
def normalize_ctype_name(typename: str) -> str:
"""Normalizes C++ name by removing unnecessary namespace prefixes and possible
pointer/reference qualification. '::' are replaced with '_'.
NOTE: Pointer decay for 'void*' is not performed.
Args:
typename (str): Name of the C++ type for normalization
Returns:
str: Normalized C++ type name.
>>> normalize_ctype_name('std::vector<cv::Point2f>&')
'vector<cv_Point2f>'
>>> normalize_ctype_name('AKAZE::DescriptorType')
'AKAZE_DescriptorType'
>>> normalize_ctype_name('std::vector<Mat>')
'vector<Mat>'
>>> normalize_ctype_name('std::string')
'string'
>>> normalize_ctype_name('void*') # keep void* as is - special case
'void*'
>>> normalize_ctype_name('Ptr<AKAZE>')
'AKAZE'
>>> normalize_ctype_name('Algorithm_Ptr')
'Algorithm'
"""
for prefix_to_remove in ("cv", "std"):
if typename.startswith(prefix_to_remove):
typename = typename[len(prefix_to_remove):]
typename = typename.replace("::", "_").lstrip("_")
if typename.endswith('&'):
typename = typename[:-1]
typename = typename.strip()
if typename == 'void*':
return typename
if is_pointer_type(typename):
# Case for "type*", "type_Ptr", "typePtr"
for suffix in ("*", "_Ptr", "Ptr"):
if typename.endswith(suffix):
return typename[:-len(suffix)]
# Case Ptr<Type>
if _is_template_instantiation(typename):
return normalize_ctype_name(
get_template_instantiation_type(typename)
)
# Case Ptr_Type
return typename.split("_", maxsplit=1)[-1]
# special normalization for several G-API Types
if typename.startswith("GArray_") or typename.startswith("GArray<"):
return "GArrayT"
if typename.startswith("GOpaque_") or typename.startswith("GOpaque<"):
return "GOpaqueT"
if typename == "GStreamerPipeline" or typename.startswith("GStreamerSource"):
return "gst_" + typename
return typename
def is_tuple_type(typename: str) -> bool:
return typename.startswith("tuple") or typename.startswith("pair")
def is_sequence_type(typename: str) -> bool:
return typename.startswith("vector")
def is_pointer_type(typename: str) -> bool:
return typename.endswith("Ptr") or typename.endswith("*") \
or typename.startswith("Ptr")
def is_union_type(typename: str) -> bool:
return typename.startswith('util_variant')
def _is_template_instantiation(typename: str) -> bool:
"""Fast, but unreliable check whenever provided typename is a template
instantiation.
Args:
typename (str): typename to check against template instantiation.
Returns:
bool: True if provided `typename` contains template instantiation,
False otherwise
"""
if "<" in typename:
assert ">" in typename, \
"Wrong template class instantiation: {}. '>' is missing".format(typename)
return True
return False
def create_type_nodes_from_template_arguments(template_args_str: str) \
-> List[TypeNode]:
"""Creates a list of type nodes corresponding to the argument types
used for template instantiation.
This method correctly addresses the situation when arguments of the input
template are also templates.
Example:
if `create_type_node` is called with
`std::tuple<std::variant<int, Point2i>, int, std::vector<int>>`
this function will be called with
`std::variant<int, Point<int>>, int, std::vector<int>`
that produces the following order of types resolution
`std::variant` ~ `Union`
`std::variant<int, Point2i>` -> `int` ~ `int` -> `Union[int, Point2i]`
`Point2i` ~ `Point2i`
`int` -> `int`
`std::vector<int>` -> `std::vector` ~ `Sequence` -> `Sequence[int]`
`int` ~ `int`
Returns:
List[TypeNode]: set of type nodes used for template instantiation.
List is empty if input string doesn't contain template instantiation.
"""
type_nodes = []
template_args_str, templated_args_types = replace_template_parameters_with_placeholders(
template_args_str
)
template_index = 0
# For each template argument
for template_arg in template_args_str.split(","):
template_arg = template_arg.strip()
# Check if argument requires type substitution
if _is_template_instantiation(template_arg):
# Reconstruct the original type
template_arg = template_arg.format(templated_args_types[template_index])
template_index += 1
# create corresponding type node
type_nodes.append(create_type_node(template_arg))
return type_nodes
def create_type_node(typename: str,
original_ctype_name: Optional[str] = None) -> TypeNode:
"""Converts C++ type name to appropriate type used in Python library API.
Conversion procedure:
1. Normalize typename: remove redundant prefixes, unify name
components delimiters, remove reference qualifications.
2. Check whenever typename has a known predefined conversion or exported
as alias e.g.
- C++ `double` -> Python `float`
- C++ `cv::Rect` -> Python `Sequence[int]`
- C++ `std::vector<char>` -> Python `np.ndarray`
return TypeNode corresponding to the appropriate type.
3. Check whenever typename is a container of types e.g. variant,
sequence or tuple. If so, select appropriate Python container type
and perform arguments conversion.
4. Create a type node corresponding to the AST node passing normalized
typename as its name.
Args:
typename (str): C++ type name to convert.
original_ctype_name (Optional[str]): Original C++ name of the type.
`original_ctype_name` == `typename` if provided argument is None.
Default is None.
Returns:
TypeNode: type node that wraps C++ type exposed to Python
>>> create_type_node('Ptr<AKAZE>').typename
'AKAZE'
>>> create_type_node('std::vector<Ptr<cv::Algorithm>>').typename
'typing.Sequence[Algorithm]'
"""
if original_ctype_name is None:
original_ctype_name = typename
typename = normalize_ctype_name(typename.strip())
# if typename is a known alias or has explicitly defined substitution
type_node = PREDEFINED_TYPES.get(typename)
if type_node is not None:
type_node.ctype_name = original_ctype_name
return type_node
# If typename is a known exported alias name (e.g. IndexParams or SearchParams)
for alias in PREDEFINED_TYPES.values():
if alias.typename == typename:
return alias
if is_union_type(typename):
union_types = get_template_instantiation_type(typename)
return UnionTypeNode(
original_ctype_name,
items=create_type_nodes_from_template_arguments(union_types)
)
# if typename refers to a sequence type e.g. vector<int>
if is_sequence_type(typename):
# Recursively convert sequence element type
if _is_template_instantiation(typename):
inner_sequence_type = create_type_node(
get_template_instantiation_type(typename)
)
else:
# Handle vector_Type cases
# maxsplit=1 is required to handle sequence of sequence e.g:
# vector_vector_Mat -> Sequence[Sequence[Mat]]
inner_sequence_type = create_type_node(typename.split("_", 1)[-1])
return SequenceTypeNode(original_ctype_name, inner_sequence_type)
# If typename refers to a heterogeneous container
# (can contain elements of different types)
if is_tuple_type(typename):
tuple_types = get_template_instantiation_type(typename)
return TupleTypeNode(
original_ctype_name,
items=create_type_nodes_from_template_arguments(tuple_types)
)
# If everything else is False, it means that input typename refers to a
# class or enum of the library.
return ASTNodeTypeNode(original_ctype_name, typename)
if __name__ == "__main__":
import doctest
doctest.testmod()
@@ -0,0 +1,180 @@
"""Contains a class used to resolve compatibility issues with old Python versions.
Typing stubs generation is available starting from Python 3.6 only.
For other versions all calls to functions are noop.
"""
import sys
import warnings
if sys.version_info >= (3, 6):
from contextlib import contextmanager
from typing import Dict, Set, Any, Sequence, Generator, Union
import traceback
from pathlib import Path
from typing_stubs_generation import (
generate_typing_stubs,
NamespaceNode,
EnumerationNode,
SymbolName,
ClassNode,
create_function_node,
create_class_node,
find_class_node,
resolve_enum_scopes
)
import functools
class FailuresWrapper:
def __init__(self, exceptions_as_warnings=True):
self.has_failure = False
self.exceptions_as_warnings = exceptions_as_warnings
def wrap_exceptions_as_warnings(self, original_func=None,
ret_type_on_failure=None):
def parametrized_wrapper(func):
@functools.wraps(func)
def wrapped_func(*args, **kwargs):
if self.has_failure:
if ret_type_on_failure is None:
return None
return ret_type_on_failure()
try:
ret_type = func(*args, **kwargs)
except Exception:
self.has_failure = True
warnings.warn(
"Typing stubs generation has failed.\n{}".format(
traceback.format_exc()
)
)
if ret_type_on_failure is None:
return None
return ret_type_on_failure()
return ret_type
if self.exceptions_as_warnings:
return wrapped_func
else:
return original_func
if original_func:
return parametrized_wrapper(original_func)
return parametrized_wrapper
@contextmanager
def delete_on_failure(self, file_path):
# type: (Path) -> Generator[None, None, None]
# There is no errors during stubs generation and file doesn't exist
if not self.has_failure and not file_path.is_file():
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.touch()
try:
# continue execution
yield
finally:
# If failure is occurred - delete file if exists
if self.has_failure and file_path.is_file():
file_path.unlink()
failures_wrapper = FailuresWrapper(exceptions_as_warnings=True)
class ClassNodeStub:
def add_base(self, base_node):
pass
class TypingStubsGenerator:
def __init__(self):
self.cv_root = NamespaceNode("cv", export_name="cv2")
self.exported_enums = {} # type: Dict[SymbolName, EnumerationNode]
self.type_hints_ignored_functions = set() # type: Set[str]
@failures_wrapper.wrap_exceptions_as_warnings
def add_enum(self, symbol_name, is_scoped_enum, entries):
# type: (SymbolName, bool, Dict[str, str]) -> None
if symbol_name in self.exported_enums:
assert symbol_name.name == "<unnamed>", \
"Trying to export 2 enums with same symbol " \
"name: {}".format(symbol_name)
enumeration_node = self.exported_enums[symbol_name]
else:
enumeration_node = EnumerationNode(symbol_name.name,
is_scoped_enum)
self.exported_enums[symbol_name] = enumeration_node
for entry_name, entry_value in entries.items():
enumeration_node.add_constant(entry_name, entry_value)
@failures_wrapper.wrap_exceptions_as_warnings
def add_ignored_function_name(self, function_name):
# type: (str) -> None
self.type_hints_ignored_functions.add(function_name)
@failures_wrapper.wrap_exceptions_as_warnings
def create_function_node(self, func_info):
# type: (Any) -> None
create_function_node(self.cv_root, func_info)
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
def find_class_node(self, class_info, namespaces):
# type: (Any, Sequence[str]) -> ClassNode
return find_class_node(
self.cv_root,
SymbolName.parse(class_info.full_original_name, namespaces),
create_missing_namespaces=True
)
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
def create_class_node(self, class_info, namespaces):
# type: (Any, Sequence[str]) -> ClassNode
return create_class_node(self.cv_root, class_info, namespaces)
def generate(self, output_path):
# type: (Union[str, Path]) -> None
output_path = Path(output_path)
py_typed_path = output_path / self.cv_root.export_name / 'py.typed'
with failures_wrapper.delete_on_failure(py_typed_path):
self._generate(output_path)
@failures_wrapper.wrap_exceptions_as_warnings
def _generate(self, output_path):
# type: (Path) -> None
resolve_enum_scopes(self.cv_root, self.exported_enums)
generate_typing_stubs(self.cv_root, output_path)
else:
class ClassNode:
def add_base(self, base_node):
pass
class TypingStubsGenerator:
def __init__(self):
self.type_hints_ignored_functions = set() # type: Set[str]
print(
'WARNING! Typing stubs can be generated only with Python 3.6 or higher. '
'Current version {}'.format(sys.version_info)
)
def add_enum(self, symbol_name, is_scoped_enum, entries):
pass
def add_ignored_function_name(self, function_name):
pass
def create_function_node(self, func_info):
pass
def create_class_node(self, class_info, namespaces):
return ClassNode()
def find_class_node(self, class_info, namespaces):
return ClassNode()
def generate(self, output_path):
pass