mirror of
https://github.com/opencv/opencv.git
synced 2026-07-25 21:33:04 +04:00
Merge branch 4.x
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "cv2_convert.hpp"
|
||||
#include "cv2_numpy.hpp"
|
||||
#include "cv2_util.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
PyTypeObject* pyopencv_Mat_TypePtr = nullptr;
|
||||
@@ -24,6 +25,26 @@ static std::string pycv_dumpArray(const T* arr, int n)
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static inline std::string getArrayTypeName(PyArrayObject* arr)
|
||||
{
|
||||
PyArray_Descr* dtype = PyArray_DESCR(arr);
|
||||
PySafeObject dtype_str(PyObject_Str(reinterpret_cast<PyObject*>(dtype)));
|
||||
if (!dtype_str)
|
||||
{
|
||||
// Fallback to typenum value
|
||||
return cv::format("%d", PyArray_TYPE(arr));
|
||||
}
|
||||
std::string type_name;
|
||||
if (!getUnicodeString(dtype_str, type_name))
|
||||
{
|
||||
// Failed to get string from bytes object - clear set TypeError and
|
||||
// fallback to typenum value
|
||||
PyErr_Clear();
|
||||
return cv::format("%d", PyArray_TYPE(arr));
|
||||
}
|
||||
return type_name;
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
// --- Mat
|
||||
@@ -80,6 +101,13 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
|
||||
|
||||
PyArrayObject* oarr = (PyArrayObject*) o;
|
||||
|
||||
if (info.outputarg && !PyArray_ISWRITEABLE(oarr))
|
||||
{
|
||||
failmsg("%s marked as output argument, but provided NumPy array "
|
||||
"marked as readonly", info.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needcopy = false, needcast = false;
|
||||
int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
|
||||
int type = typenum == NPY_UBYTE ? CV_8U :
|
||||
@@ -102,7 +130,9 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
|
||||
}
|
||||
else
|
||||
{
|
||||
failmsg("%s data type = %d is not supported", info.name, typenum);
|
||||
const std::string dtype_name = getArrayTypeName(oarr);
|
||||
failmsg("%s data type = %s is not supported", info.name,
|
||||
dtype_name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -728,6 +758,26 @@ PyObject* pyopencv_from(const Rect2d& r)
|
||||
|
||||
// --- RotatedRect
|
||||
|
||||
static inline bool convertToRotatedRect(PyObject* obj, RotatedRect& dst)
|
||||
{
|
||||
PyObject* type = PyObject_Type(obj);
|
||||
if (getPyObjectAttr(type, "__module__") == MODULESTR &&
|
||||
getPyObjectNameAttr(type) == "RotatedRect")
|
||||
{
|
||||
struct pyopencv_RotatedRect_t
|
||||
{
|
||||
PyObject_HEAD
|
||||
cv::RotatedRect v;
|
||||
};
|
||||
dst = reinterpret_cast<pyopencv_RotatedRect_t*>(obj)->v;
|
||||
|
||||
Py_DECREF(type);
|
||||
return true;
|
||||
}
|
||||
Py_DECREF(type);
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info)
|
||||
{
|
||||
@@ -735,6 +785,12 @@ bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// This is a workaround for compatibility with an initialization from tuple.
|
||||
// Allows import RotatedRect as an object.
|
||||
if (convertToRotatedRect(obj, dst))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!PySequence_Check(obj))
|
||||
{
|
||||
failmsg("Can't parse '%s' as RotatedRect."
|
||||
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
|
||||
/**
|
||||
* Light weight RAII wrapper for `PyObject*` owning references.
|
||||
* In comparisson to C++11 `std::unique_ptr` with custom deleter, it provides
|
||||
* 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
|
||||
@@ -70,6 +70,10 @@ public:
|
||||
return &obj_;
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return obj_ != nullptr;
|
||||
}
|
||||
|
||||
PyObject* release()
|
||||
{
|
||||
PyObject* obj = obj_;
|
||||
|
||||
@@ -231,6 +231,8 @@ simple_argtype_mapping = {
|
||||
"c_string": ArgTypeInfo("char*", FormatStrings.string, '(char*)""'),
|
||||
"string": ArgTypeInfo("std::string", FormatStrings.object, None, True),
|
||||
"Stream": ArgTypeInfo("Stream", FormatStrings.object, 'Stream::Null()', True),
|
||||
"cuda_Stream": ArgTypeInfo("cuda::Stream", FormatStrings.object, "cuda::Stream::Null()", True),
|
||||
"cuda_GpuMat": ArgTypeInfo("cuda::GpuMat", FormatStrings.object, "cuda::GpuMat()", True),
|
||||
"UMat": ArgTypeInfo("UMat", FormatStrings.object, 'UMat()', True), # FIXIT: switch to CV_EXPORTS_W_SIMPLE as UMat is already a some kind of smart pointer
|
||||
}
|
||||
|
||||
@@ -509,14 +511,17 @@ class ArgInfo(object):
|
||||
return self.enclosing_arg.name + '.' + self.name
|
||||
|
||||
def isbig(self):
|
||||
return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector")
|
||||
return self.tp in ["Mat", "vector_Mat",
|
||||
"cuda::GpuMat", "cuda_GpuMat", "GpuMat",
|
||||
"vector_GpuMat", "vector_cuda_GpuMat",
|
||||
"UMat", "vector_UMat"] # or self.tp.startswith("vector")
|
||||
|
||||
def crepr(self):
|
||||
return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg)
|
||||
|
||||
|
||||
def find_argument_class_info(argument_type, function_namespace,
|
||||
function_class_name, known_classes):
|
||||
function_class_name, known_classes):
|
||||
# type: (str, str, str, dict[str, ClassInfo]) -> ClassInfo | None
|
||||
"""Tries to find corresponding class info for the provided argument type
|
||||
|
||||
|
||||
@@ -98,10 +98,10 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str)
|
||||
}
|
||||
|
||||
static inline
|
||||
std::string getPyObjectNameAttr(PyObject* obj)
|
||||
std::string getPyObjectAttr(PyObject* obj, const char* attrName)
|
||||
{
|
||||
std::string obj_name;
|
||||
PyObject* cls_name_obj = PyObject_GetAttrString(obj, "__name__");
|
||||
PyObject* cls_name_obj = PyObject_GetAttrString(obj, attrName);
|
||||
if (cls_name_obj && !getUnicodeString(cls_name_obj, obj_name)) {
|
||||
obj_name.clear();
|
||||
}
|
||||
@@ -117,6 +117,12 @@ std::string getPyObjectNameAttr(PyObject* obj)
|
||||
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
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
__all__ = [
|
||||
"apply_manual_api_refinement"
|
||||
]
|
||||
|
||||
from typing import cast, Sequence, Callable, Iterable
|
||||
|
||||
from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode,
|
||||
ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode,
|
||||
AggregatedTypeNode, CallableTypeNode, AnyTypeNode,
|
||||
TupleTypeNode, UnionTypeNode, ProtocolClassNode,
|
||||
DictTypeNode, ClassTypeNode)
|
||||
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"
|
||||
|
||||
"""
|
||||
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 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_name: 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 overload in function.overloads:
|
||||
arg_idx = _find_argument_index(overload.arguments, arg_name)
|
||||
# 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)
|
||||
)
|
||||
|
||||
return _make_optional_arg
|
||||
|
||||
|
||||
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) -> int:
|
||||
for i, arg in enumerate(arguments):
|
||||
if arg.name == name:
|
||||
return i
|
||||
raise RuntimeError(
|
||||
f"Failed to find argument with name: '{name}' in {arguments}"
|
||||
)
|
||||
|
||||
|
||||
NODES_TO_REFINE = {
|
||||
SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"),
|
||||
SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"),
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import NamedTuple, Sequence, Tuple, Union, List, Dict
|
||||
from typing import (NamedTuple, Sequence, Tuple, Union, List,
|
||||
Dict, Callable, Optional, Generator, cast)
|
||||
import keyword
|
||||
|
||||
from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode,
|
||||
@@ -142,13 +143,24 @@ because 'GOpaque' class is not registered yet
|
||||
return scope
|
||||
|
||||
|
||||
def find_class_node(root: NamespaceNode, full_class_name: str,
|
||||
namespaces: Sequence[str]) -> ClassNode:
|
||||
symbol_name = SymbolName.parse(full_class_name, namespaces)
|
||||
scope = find_scope(root, symbol_name)
|
||||
if symbol_name.name not in scope.classes:
|
||||
raise SymbolNotFoundError("Can't find {} in its scope".format(symbol_name))
|
||||
return scope.classes[symbol_name.name]
|
||||
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],
|
||||
@@ -192,9 +204,7 @@ def create_function_node_in_scope(scope: Union[NamespaceNode, ClassNode],
|
||||
outlist = variant.py_outlist
|
||||
for _, argno in outlist:
|
||||
assert argno >= 0, \
|
||||
"Logic Error! Outlist contains function return type: {}".format(
|
||||
outlist
|
||||
)
|
||||
f"Logic Error! Outlist contains function return type: {outlist}"
|
||||
|
||||
ret_types.append(create_type_node(variant.args[argno].tp))
|
||||
|
||||
@@ -323,12 +333,18 @@ def resolve_enum_scopes(root: NamespaceNode,
|
||||
enum_node.parent = scope
|
||||
|
||||
|
||||
def get_enclosing_namespace(node: ASTNode) -> NamespaceNode:
|
||||
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.
|
||||
@@ -360,10 +376,61 @@ def get_enclosing_namespace(node: ASTNode) -> NamespaceNode:
|
||||
"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()
|
||||
|
||||
@@ -2,19 +2,26 @@ __all__ = ("generate_typing_stubs", )
|
||||
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import (Generator, Type, Callable, NamedTuple, Union, Set, Dict,
|
||||
Collection)
|
||||
import re
|
||||
from typing import (Callable, NamedTuple, Union, Set, Dict,
|
||||
Collection, Tuple, List)
|
||||
import warnings
|
||||
|
||||
from .ast_utils import get_enclosing_namespace
|
||||
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, NamespaceNode, ClassNode, FunctionNode,
|
||||
EnumerationNode, ConstantNode)
|
||||
from .nodes import (ASTNode, ASTNodeType, NamespaceNode, ClassNode,
|
||||
FunctionNode, EnumerationNode, ConstantNode,
|
||||
ProtocolClassNode)
|
||||
|
||||
from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode,
|
||||
AggregatedTypeNode)
|
||||
AggregatedTypeNode, ASTNodeTypeNode,
|
||||
ConditionalAliasTypeNode, PrimitiveTypeNode)
|
||||
|
||||
|
||||
def generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
@@ -45,6 +52,18 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
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.
|
||||
@@ -70,10 +89,11 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
# checked and at least 1 node is still unresolved.
|
||||
root.resolve_type_nodes()
|
||||
_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):
|
||||
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)
|
||||
|
||||
@@ -82,19 +102,24 @@ def _generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
|
||||
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 constants section, because constants don't impose any dependencies
|
||||
_generate_section_stub(StubSection("# Constants", ConstantNode), root,
|
||||
output_stream, 0)
|
||||
_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", EnumerationNode),
|
||||
root, output_stream, 0)
|
||||
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):
|
||||
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:
|
||||
@@ -112,14 +137,15 @@ def _generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
|
||||
class StubSection(NamedTuple):
|
||||
name: str
|
||||
node_type: Type[ASTNode]
|
||||
node_type: ASTNodeType
|
||||
|
||||
|
||||
STUB_SECTIONS = (
|
||||
StubSection("# Constants", ConstantNode),
|
||||
# StubSection("# Enumerations", EnumerationNode), # Skipped for now (special rules)
|
||||
StubSection("# Classes", ClassNode),
|
||||
StubSection("# Functions", FunctionNode)
|
||||
StubSection("# Constants", ASTNodeType.Constant),
|
||||
# Enumerations are skipped due to special handling rules
|
||||
# StubSection("# Enumerations", ASTNodeType.Enumeration),
|
||||
StubSection("# Classes", ASTNodeType.Class),
|
||||
StubSection("# Functions", ASTNodeType.Function)
|
||||
)
|
||||
|
||||
|
||||
@@ -228,9 +254,9 @@ def _generate_class_stub(class_node: ClassNode, output_stream: StringIO,
|
||||
else:
|
||||
bases.append(base.export_name)
|
||||
|
||||
inheritance_str = "({})".format(
|
||||
', '.join(bases)
|
||||
)
|
||||
inheritance_str = f"({', '.join(bases)})"
|
||||
elif isinstance(class_node, ProtocolClassNode):
|
||||
inheritance_str = "(Protocol)"
|
||||
else:
|
||||
inheritance_str = ""
|
||||
|
||||
@@ -269,7 +295,8 @@ def _generate_class_stub(class_node: ClassNode, output_stream: StringIO,
|
||||
|
||||
def _generate_constant_stub(constant_node: ConstantNode,
|
||||
output_stream: StringIO, indent: int = 0,
|
||||
extra_export_prefix: str = "") -> None:
|
||||
extra_export_prefix: str = "",
|
||||
generate_uppercase_version: bool = True) -> Tuple[str, ...]:
|
||||
"""Generates stub for the provided constant node.
|
||||
|
||||
Args:
|
||||
@@ -277,18 +304,36 @@ def _generate_constant_stub(constant_node: ConstantNode,
|
||||
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
|
||||
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.
|
||||
"""
|
||||
|
||||
output_stream.write(
|
||||
"{indent}{prefix}{name}: {value_type}\n".format(
|
||||
prefix=extra_export_prefix,
|
||||
name=constant_node.export_name,
|
||||
value_type=constant_node.value_type,
|
||||
indent=" " * indent
|
||||
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,
|
||||
@@ -353,18 +398,20 @@ def _generate_enumeration_stub(enumeration_node: EnumerationNode,
|
||||
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():
|
||||
_generate_constant_stub(entry, output_stream, indent, entries_extra_prefix)
|
||||
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 # One of [{entries}]\n\n".format(
|
||||
'{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(entry.export_name
|
||||
for entry in enumeration_node.constants.values()),
|
||||
entries=", ".join(generated_constants_entries),
|
||||
indent=" " * indent
|
||||
)
|
||||
)
|
||||
@@ -500,35 +547,12 @@ def check_overload_presence(node: Union[NamespaceNode, ClassNode]) -> bool:
|
||||
otherwise.
|
||||
"""
|
||||
for func_node in node.functions.values():
|
||||
if len(func_node.overloads):
|
||||
if len(func_node.overloads) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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]) \
|
||||
-> Generator[FunctionNode, None, None]:
|
||||
for func in node.functions.values():
|
||||
yield func
|
||||
for cls in node.classes.values():
|
||||
yield from _for_each_function(cls)
|
||||
|
||||
|
||||
def _for_each_function_overload(node: Union[NamespaceNode, ClassNode]) \
|
||||
-> Generator[FunctionNode.Overload, None, None]:
|
||||
for func in _for_each_function(node):
|
||||
for overload in func.overloads:
|
||||
yield overload
|
||||
|
||||
|
||||
def _collect_required_imports(root: NamespaceNode) -> Set[str]:
|
||||
def _collect_required_imports(root: NamespaceNode) -> Collection[str]:
|
||||
"""Collects all imports required for classes and functions typing stubs
|
||||
declarations.
|
||||
|
||||
@@ -536,8 +560,8 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]:
|
||||
root (NamespaceNode): Namespace node to collect imports for
|
||||
|
||||
Returns:
|
||||
Set[str]: Collection of unique `import smth` statements required for
|
||||
classes and function declarations of `root` node.
|
||||
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]):
|
||||
@@ -550,7 +574,8 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]:
|
||||
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
|
||||
for cls in _for_each_class(root):
|
||||
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")
|
||||
@@ -564,12 +589,15 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]:
|
||||
required_imports.add(
|
||||
"import " + base_namespace.full_export_name
|
||||
)
|
||||
if isinstance(cls, ProtocolClassNode):
|
||||
has_protocol = True
|
||||
|
||||
if has_overload:
|
||||
required_imports.add("import 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):
|
||||
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,
|
||||
@@ -579,7 +607,74 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]:
|
||||
if root_import in required_imports:
|
||||
required_imports.remove(root_import)
|
||||
|
||||
return required_imports
|
||||
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)
|
||||
|
||||
# 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],
|
||||
@@ -592,7 +687,7 @@ def _write_required_imports(required_imports: Collection[str],
|
||||
output_stream (StringIO): Output stream for import statements.
|
||||
"""
|
||||
|
||||
for required_import in sorted(required_imports):
|
||||
for required_import in required_imports:
|
||||
output_stream.write(required_import)
|
||||
output_stream.write("\n")
|
||||
if len(required_imports):
|
||||
@@ -614,31 +709,82 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
|
||||
f"Provided type node '{type_node.ctype_name}' is not an aggregated type"
|
||||
|
||||
for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node):
|
||||
register_alias(PREDEFINED_TYPES[item.ctype_name]) # type: ignore
|
||||
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", )
|
||||
)
|
||||
|
||||
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}"""'
|
||||
for required_import in alias_node.required_definition_imports:
|
||||
required_imports.add(required_import)
|
||||
|
||||
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
|
||||
@@ -646,6 +792,12 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
|
||||
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")
|
||||
@@ -655,11 +807,14 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
|
||||
|
||||
_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(alias_name)
|
||||
output_stream.write(" = ")
|
||||
output_stream.write(alias_type)
|
||||
output_stream.write("\n")
|
||||
output_stream.write(f"{alias_name} = {alias_type}\n")
|
||||
|
||||
TypeNode.compatible_to_runtime_usage = False
|
||||
(output_path / "__init__.py").write_text(output_stream.getvalue())
|
||||
@@ -669,8 +824,8 @@ StubGenerator = Callable[[ASTNode, StringIO, int], None]
|
||||
|
||||
|
||||
NODE_TYPE_TO_STUB_GENERATOR = {
|
||||
ClassNode: _generate_class_stub,
|
||||
ConstantNode: _generate_constant_stub,
|
||||
EnumerationNode: _generate_enumeration_stub,
|
||||
FunctionNode: _generate_function_stub
|
||||
ASTNodeType.Class: _generate_class_stub,
|
||||
ASTNodeType.Constant: _generate_constant_stub,
|
||||
ASTNodeType.Enumeration: _generate_enumeration_stub,
|
||||
ASTNodeType.Function: _generate_function_stub
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from .node import ASTNode
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from .namespace_node import NamespaceNode
|
||||
from .class_node import ClassNode, ClassProperty
|
||||
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,
|
||||
AggregatedTypeNode, NDArrayTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
|
||||
CallableTypeNode, DictTypeNode, ClassTypeNode
|
||||
)
|
||||
|
||||
@@ -63,8 +63,9 @@ class ClassNode(ASTNode):
|
||||
return 1 + sum(base.weight for base in self.bases)
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[Type[ASTNode], ...]:
|
||||
return (ClassNode, FunctionNode, EnumerationNode, ConstantNode)
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return (ASTNodeType.Class, ASTNodeType.Function,
|
||||
ASTNodeType.Enumeration, ASTNodeType.Constant)
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
@@ -72,19 +73,19 @@ class ClassNode(ASTNode):
|
||||
|
||||
@property
|
||||
def classes(self) -> Dict[str, "ClassNode"]:
|
||||
return self._children[ClassNode]
|
||||
return self._children[ASTNodeType.Class]
|
||||
|
||||
@property
|
||||
def functions(self) -> Dict[str, FunctionNode]:
|
||||
return self._children[FunctionNode]
|
||||
return self._children[ASTNodeType.Function]
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Dict[str, EnumerationNode]:
|
||||
return self._children[EnumerationNode]
|
||||
return self._children[ASTNodeType.Enumeration]
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ConstantNode]
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_class(self, name: str,
|
||||
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
|
||||
@@ -179,3 +180,11 @@ class ClassNode(ASTNode):
|
||||
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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Type, Optional, Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
|
||||
@@ -11,9 +11,10 @@ class ConstantNode(ASTNode):
|
||||
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[Type[ASTNode], ...]:
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return ()
|
||||
|
||||
@property
|
||||
@@ -22,7 +23,7 @@ class ConstantNode(ASTNode):
|
||||
|
||||
@property
|
||||
def value_type(self) -> str:
|
||||
return 'int'
|
||||
return self._value_type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Constant('{}' exported as '{}': {})".format(
|
||||
|
||||
@@ -18,8 +18,8 @@ class EnumerationNode(ASTNode):
|
||||
self.is_scoped = is_scoped
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[Type[ASTNode], ...]:
|
||||
return (ConstantNode, )
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return (ASTNodeType.Constant, )
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
@@ -27,7 +27,7 @@ class EnumerationNode(ASTNode):
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ConstantNode]
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_constant(self, name: str, value: str) -> ConstantNode:
|
||||
return self._add_child(ConstantNode, name, value=value)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import NamedTuple, Sequence, Type, Optional, Tuple, List
|
||||
from typing import NamedTuple, Sequence, Optional, Tuple, List
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from .type_node import TypeNode, NoneTypeNode, TypeResolutionError
|
||||
@@ -10,10 +10,12 @@ class FunctionNode(ASTNode):
|
||||
This class defines an overload set rather then function itself, because
|
||||
function without overloads is represented as FunctionNode with 1 overload.
|
||||
"""
|
||||
class Arg(NamedTuple):
|
||||
name: str
|
||||
type_node: Optional[TypeNode] = None
|
||||
default_value: Optional[str] = None
|
||||
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]:
|
||||
@@ -24,8 +26,18 @@ class FunctionNode(ASTNode):
|
||||
return self.type_node.relative_typename(root)
|
||||
return None
|
||||
|
||||
class RetType(NamedTuple):
|
||||
type_node: TypeNode = NoneTypeNode("void")
|
||||
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:
|
||||
@@ -34,6 +46,12 @@ class FunctionNode(ASTNode):
|
||||
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
|
||||
@@ -80,7 +98,7 @@ class FunctionNode(ASTNode):
|
||||
return ASTNodeType.Function
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[Type[ASTNode], ...]:
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return ()
|
||||
|
||||
def add_overload(self, arguments: Sequence["FunctionNode.Arg"] = (),
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
from typing import Type, Iterable, Sequence, Tuple, Optional, Dict
|
||||
import itertools
|
||||
import weakref
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from .class_node import ClassNode, ClassProperty
|
||||
from .function_node import FunctionNode
|
||||
from .enumeration_node import EnumerationNode
|
||||
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
|
||||
|
||||
|
||||
@@ -18,34 +17,45 @@ class NamespaceNode(ASTNode):
|
||||
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[Type[ASTNode], ...]:
|
||||
return (NamespaceNode, ClassNode, FunctionNode,
|
||||
EnumerationNode, ConstantNode)
|
||||
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[NamespaceNode]
|
||||
return self._children[ASTNodeType.Namespace]
|
||||
|
||||
@property
|
||||
def classes(self) -> Dict[str, ClassNode]:
|
||||
return self._children[ClassNode]
|
||||
return self._children[ASTNodeType.Class]
|
||||
|
||||
@property
|
||||
def functions(self) -> Dict[str, FunctionNode]:
|
||||
return self._children[FunctionNode]
|
||||
return self._children[ASTNodeType.Function]
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Dict[str, EnumerationNode]:
|
||||
return self._children[EnumerationNode]
|
||||
return self._children[ASTNodeType.Enumeration]
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ConstantNode]
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_namespace(self, name: str) -> "NamespaceNode":
|
||||
return self._add_child(NamespaceNode, name)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import abc
|
||||
import enum
|
||||
import itertools
|
||||
from typing import (Iterator, Type, TypeVar, Iterable, Dict,
|
||||
from typing import (Iterator, Type, TypeVar, Dict,
|
||||
Optional, Tuple, DefaultDict)
|
||||
from collections import defaultdict
|
||||
|
||||
@@ -70,22 +70,22 @@ class ASTNode:
|
||||
self._parent: Optional["ASTNode"] = None
|
||||
self.parent = parent
|
||||
self.is_exported = True
|
||||
self._children: DefaultDict[NodeType, NameToNode] = defaultdict(dict)
|
||||
self._children: DefaultDict[ASTNodeType, NameToNode] = defaultdict(dict)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "{}('{}' exported as '{}')".format(
|
||||
type(self).__name__.replace("Node", ""), self.name, self.export_name
|
||||
self.node_type.name, self.name, self.export_name
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
@abc.abstractproperty
|
||||
def children_types(self) -> Tuple[Type["ASTNode"], ...]:
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
"""Set of ASTNode types that are allowed to be children of this node
|
||||
|
||||
Returns:
|
||||
Tuple[Type[ASTNode], ...]: Types of children nodes
|
||||
Tuple[ASTNodeType, ...]: Types of children nodes
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -99,6 +99,9 @@ class ASTNode:
|
||||
"""
|
||||
pass
|
||||
|
||||
def node_type_name(self) -> str:
|
||||
return f"{self.node_type.name}::{self.name}"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__name
|
||||
@@ -126,11 +129,11 @@ class ASTNode:
|
||||
"but got: {}".format(type(value))
|
||||
|
||||
if value is not None:
|
||||
value.__check_child_before_add(type(self), self.name)
|
||||
value.__check_child_before_add(self, self.name)
|
||||
|
||||
# Detach from previous parent
|
||||
if self._parent is not None:
|
||||
self._parent._children[type(self)].pop(self.name)
|
||||
self._parent._children[self.node_type].pop(self.name)
|
||||
|
||||
if value is None:
|
||||
self._parent = None
|
||||
@@ -138,28 +141,26 @@ class ASTNode:
|
||||
|
||||
# Set a weak reference to a new parent and add self to its children
|
||||
self._parent = weakref.proxy(value)
|
||||
value._children[type(self)][self.name] = self
|
||||
value._children[self.node_type][self.name] = self
|
||||
|
||||
def __check_child_before_add(self, child_type: Type[ASTNodeSubtype],
|
||||
def __check_child_before_add(self, child: ASTNodeSubtype,
|
||||
name: str) -> None:
|
||||
assert len(self.children_types) > 0, \
|
||||
"Trying to add child node '{}::{}' to node '{}::{}' " \
|
||||
"that can't have children nodes".format(child_type.__name__, name,
|
||||
type(self).__name__,
|
||||
self.name)
|
||||
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_type in self.children_types, \
|
||||
"Trying to add child node '{}::{}' to node '{}::{}' " \
|
||||
assert child.node_type in self.children_types, \
|
||||
"Trying to add child node '{}' to node '{}' " \
|
||||
"that supports only ({}) as its children types".format(
|
||||
child_type.__name__, name, type(self).__name__, self.name,
|
||||
",".join(t.__name__ for t in self.children_types)
|
||||
child.node_type_name, self.node_type_name,
|
||||
",".join(t.name for t in self.children_types)
|
||||
)
|
||||
|
||||
if self._find_child(child_type, name) is not None:
|
||||
if self._find_child(child.node_type, name) is not None:
|
||||
raise ValueError(
|
||||
"Node '{}::{}' already has a child '{}::{}'".format(
|
||||
type(self).__name__, self.name, child_type.__name__, name
|
||||
)
|
||||
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,
|
||||
@@ -180,15 +181,14 @@ class ASTNode:
|
||||
Returns:
|
||||
ASTNodeSubtype: Created ASTNode
|
||||
"""
|
||||
self.__check_child_before_add(child_type, name)
|
||||
return child_type(name, parent=self, **kwargs)
|
||||
|
||||
def _find_child(self, child_type: Type[ASTNodeSubtype],
|
||||
def _find_child(self, child_type: ASTNodeType,
|
||||
name: str) -> Optional[ASTNodeSubtype]:
|
||||
"""Looks for child node with the given type and name.
|
||||
|
||||
Args:
|
||||
child_type (Type[ASTNodeSubtype]): Type of the child node.
|
||||
child_type (ASTNodeType): Type of the child node.
|
||||
name (str): Name of the child node.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -307,14 +307,31 @@ class AliasTypeNode(TypeNode):
|
||||
return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc)
|
||||
|
||||
@classmethod
|
||||
def array_(cls, ctype_name: str, shape: Optional[Tuple[int, ...]],
|
||||
dtype: Optional[str] = None, export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None):
|
||||
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):
|
||||
"""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 = "Shape: " + str(shape)
|
||||
doc = f"NDArray(shape={shape}, dtype={dtype})"
|
||||
else:
|
||||
doc += ". Shape: " + str(shape)
|
||||
return cls(ctype_name, NDArrayTypeNode(ctype_name, shape, dtype),
|
||||
doc += f". NDArray(shape={shape}, dtype={dtype})"
|
||||
return cls(ctype_name, AliasRefTypeNode(array_ref_name),
|
||||
export_name, doc)
|
||||
|
||||
@classmethod
|
||||
@@ -376,24 +393,115 @@ class AliasTypeNode(TypeNode):
|
||||
export_name, doc)
|
||||
|
||||
|
||||
class NDArrayTypeNode(TypeNode):
|
||||
"""Type node representing NumPy ndarray.
|
||||
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, shape: Optional[Tuple[int, ...]] = None,
|
||||
dtype: Optional[str] = None) -> None:
|
||||
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.shape = shape
|
||||
self.dtype = dtype
|
||||
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:
|
||||
return "numpy.ndarray[{shape}, numpy.dtype[{dtype}]]".format(
|
||||
# NOTE: Shape is not fully supported yet
|
||||
# shape=self.shape if self.shape is not None else "typing.Any",
|
||||
shape="typing.Any",
|
||||
dtype=self.dtype if self.dtype is not None else "numpy.generic"
|
||||
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 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",)
|
||||
)
|
||||
|
||||
|
||||
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"
|
||||
@@ -417,6 +525,10 @@ class ASTNodeTypeNode(TypeNode):
|
||||
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:
|
||||
@@ -557,13 +669,14 @@ class ContainerTypeNode(AggregatedTypeNode):
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
yield "import typing"
|
||||
return super().required_definition_imports
|
||||
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"
|
||||
return super().required_usage_imports
|
||||
yield from super().required_usage_imports
|
||||
|
||||
@abc.abstractproperty
|
||||
def type_format(self) -> str:
|
||||
pass
|
||||
@@ -726,6 +839,21 @@ class CallableTypeNode(AggregatedTypeNode):
|
||||
yield from super().required_usage_imports
|
||||
|
||||
|
||||
class ClassTypeNode(ContainerTypeNode):
|
||||
"""Type node representing types themselves (refer to typing.Type)
|
||||
"""
|
||||
def __init__(self, value: TypeNode) -> None:
|
||||
super().__init__(value.ctype_name, (value,))
|
||||
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
return "typing.Type[{}]"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
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`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from .nodes.type_node import (
|
||||
AliasTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
|
||||
ASTNodeTypeNode, NDArrayTypeNode, NoneTypeNode, SequenceTypeNode,
|
||||
TupleTypeNode, UnionTypeNode, AnyTypeNode
|
||||
TupleTypeNode, UnionTypeNode, AnyTypeNode, ConditionalAliasTypeNode
|
||||
)
|
||||
|
||||
# Set of predefined types used to cover cases when library doesn't
|
||||
@@ -22,6 +22,10 @@ _PREDEFINED_TYPES = (
|
||||
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.float_("float"),
|
||||
PrimitiveTypeNode.float_("double"),
|
||||
@@ -30,12 +34,15 @@ _PREDEFINED_TYPES = (
|
||||
PrimitiveTypeNode.str_("char"),
|
||||
PrimitiveTypeNode.str_("String"),
|
||||
PrimitiveTypeNode.str_("c_string"),
|
||||
ConditionalAliasTypeNode.numpy_array_("NumPyArrayGeneric"),
|
||||
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"),
|
||||
NDArrayTypeNode("Mat")),
|
||||
AliasRefTypeNode("NumPyArrayGeneric")),
|
||||
export_name="MatLike"
|
||||
),
|
||||
AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()),
|
||||
@@ -137,10 +144,22 @@ _PREDEFINED_TYPES = (
|
||||
ASTNodeTypeNode("gapi.wip.draw.Mosaic"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Poly"))),
|
||||
SequenceTypeNode("Prims", AliasRefTypeNode("Prim")),
|
||||
AliasTypeNode.array_("Matx33f", (3, 3), "numpy.float32"),
|
||||
AliasTypeNode.array_("Matx33d", (3, 3), "numpy.float64"),
|
||||
AliasTypeNode.array_("Matx44f", (4, 4), "numpy.float32"),
|
||||
AliasTypeNode.array_("Matx44d", (4, 4), "numpy.float64"),
|
||||
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"),
|
||||
TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"),
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
|
||||
@@ -46,10 +47,12 @@ if sys.version_info >= (3, 6):
|
||||
|
||||
try:
|
||||
ret_type = func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
self.has_failure = True
|
||||
warnings.warn(
|
||||
'Typing stubs generation has failed. Reason: {}'.format(e)
|
||||
"Typing stubs generation has failed.\n{}".format(
|
||||
traceback.format_exc()
|
||||
)
|
||||
)
|
||||
if ret_type_on_failure is None:
|
||||
return None
|
||||
@@ -120,7 +123,11 @@ if sys.version_info >= (3, 6):
|
||||
@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, class_info.full_original_name, namespaces)
|
||||
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):
|
||||
|
||||
@@ -219,6 +219,25 @@ class Arguments(NewOpenCVTests):
|
||||
#res6 = cv.utils.dumpInputArray([a, b])
|
||||
#self.assertEqual(res6, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_32FC1 dims(0)=4 size(0)=[2 3 4 5]")
|
||||
|
||||
def test_unsupported_numpy_data_types_string_description(self):
|
||||
for dtype in (object, str, np.complex128):
|
||||
test_array = np.zeros((4, 4, 3), dtype=dtype)
|
||||
msg = ".*type = {} is not supported".format(test_array.dtype)
|
||||
if sys.version_info[0] < 3:
|
||||
self.assertRaisesRegexp(
|
||||
Exception, msg, cv.utils.dumpInputArray, test_array
|
||||
)
|
||||
else:
|
||||
self.assertRaisesRegex(
|
||||
Exception, msg, cv.utils.dumpInputArray, test_array
|
||||
)
|
||||
|
||||
def test_numpy_writeable_flag_is_preserved(self):
|
||||
array = np.zeros((10, 10, 1), dtype=np.uint8)
|
||||
array.setflags(write=False)
|
||||
with self.assertRaises(Exception):
|
||||
cv.rectangle(array, (0, 0), (5, 5), (255), 2)
|
||||
|
||||
def test_20968(self):
|
||||
pixel = np.uint8([[[40, 50, 200]]])
|
||||
_ = cv.cvtColor(pixel, cv.COLOR_RGB2BGR) # should not raise exception
|
||||
@@ -482,6 +501,27 @@ class Arguments(NewOpenCVTests):
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
|
||||
def test_wrap_rotated_rect(self):
|
||||
center = (34.5, 52.)
|
||||
size = (565.0, 140.0)
|
||||
angle = -177.5
|
||||
rect1 = cv.RotatedRect(center, size, angle)
|
||||
self.assertEqual(rect1.center, center)
|
||||
self.assertEqual(rect1.size, size)
|
||||
self.assertEqual(rect1.angle, angle)
|
||||
|
||||
pts = [[ 319.7845, -5.6109037],
|
||||
[ 313.6778, 134.25586],
|
||||
[-250.78448, 109.6109],
|
||||
[-244.6778, -30.25586]]
|
||||
self.assertLess(np.max(np.abs(rect1.points() - pts)), 1e-4)
|
||||
|
||||
rect2 = cv.RotatedRect(pts[0], pts[1], pts[2])
|
||||
_, inter_pts = cv.rotatedRectangleIntersection(rect1, rect2)
|
||||
self.assertLess(np.max(np.abs(inter_pts.reshape(-1, 2) - pts)), 1e-4)
|
||||
|
||||
|
||||
def test_parse_to_rotated_rect_not_convertible(self):
|
||||
for not_convertible in ([], (), np.array([]), (123, (45, 34), 1), {1: 2, 3: 4}, 123,
|
||||
np.array([[123, 123, 14], [1, 3], 56], dtype=object), '123'):
|
||||
|
||||
Reference in New Issue
Block a user