8000 feat: provide cv2.typing aliases at runtime by VadimLevin · Pull Request #23798 · opencv/opencv · GitHub
[go: up one dir, main page]

Skip to content

feat: provide cv2.typing aliases at runtime #23798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions modules/python/src2/typing_stubs_generation/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,7 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
"""
def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
assert isinstance(type_node, AggregatedTypeNode), \
"Provided type node '{}' is not an aggregated type".format(
type_node.ctype_name
)
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
Expand All @@ -631,8 +629,8 @@ def register_alias(alias_node: AliasTypeNode) -> None:
aliases[typename] = alias_node.value.full_typename.replace(
root.export_name + ".typing.", ""
)
if alias_node.comment is not None:
aliases[typename] += " # " + alias_node.comment
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)

Expand All @@ -643,12 +641,18 @@ def register_alias(alias_node: AliasTypeNode) -> None:
aliases: Dict[str, str] = {}

# Resolve each node and register aliases
TypeNode.compatible_to_runtime_usage = True
for node in PREDEFINED_TYPES.values():
node.resolve(root)
if isinstance(node, AliasTypeNode):
register_alias(node)

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)

for alias_name, alias_type in aliases.items():
Expand All @@ -657,7 +661,8 @@ def register_alias(alias_node: AliasTypeNode) -> None:
output_stream.write(alias_type)
output_stream.write("\n")

(output_path / "__init__.pyi").write_text(output_stream.getvalue())
TypeNode.compatible_to_runtime_usage = False
(output_path / "__init__.py").write_text(output_stream.getvalue())


StubGenerator = Callable[[ASTNode, StringIO, int], None]
Expand Down
123 changes: 76 additions & 47 deletions modules/python/src2/typing_stubs_generation/nodes/type_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ class TypeNode(abc.ABC):
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) -> None:
self.ctype_name = ctype_name

Expand Down Expand Up @@ -247,11 +258,11 @@ class AliasTypeNode(TypeNode):
"""
def __init__(self, ctype_name: str, value: TypeNode,
export_name: Optional[str] = None,
comment: Optional[str] = None) -> None:
doc: Optional[str] = None) -> None:
super().__init__(ctype_name)
self.value = value
self._export_name = export_name
self.comment = comment
self.doc = doc

@property
def typename(self) -> str:
Expand Down Expand Up @@ -287,82 +298,82 @@ def resolve(self, root: ASTNode):

@classmethod
def int_(cls, ctype_name: str, export_name: Optional[str] = None,
comment: Optional[str] = None):
return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, comment)
doc: Optional[str] = None):
return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc)

@classmethod
def float_(cls, ctype_name: str, export_name: Optional[str] = None,
comment: Optional[str] = None):
return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, comment)
doc: Optional[str] = None):
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,
comment: Optional[str] = None):
if comment is None:
comment = "Shape: " + str(shape)
doc: Optional[str] = None):
if doc is None:
doc = "Shape: " + str(shape)
else:
comment += ". Shape: " + str(shape)
doc += ". Shape: " + str(shape)
return cls(ctype_name, NDArrayTypeNode(ctype_name, shape, dtype),
export_name, comment)
export_name, doc)

@classmethod
def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None,
comment: Optional[str] = None):
doc: Optional[str] = None):
return cls(ctype_name, UnionTypeNode(ctype_name, items),
export_name, comment)
export_name, doc)

@classmethod
def optional_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None,
comment: Optional[str] = None):
return 8000 cls(ctype_name, OptionalTypeNode(item), export_name, comment)
doc: Optional[str] = None):
return cls(ctype_name, OptionalTypeNode(item), export_name, doc)

@classmethod
def sequence_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None,
comment: Optional[str] = None):
doc: Optional[str] = None):
return cls(ctype_name, SequenceTypeNode(ctype_name, item),
export_name, comment)
export_name, doc)

@classmethod
def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None,
comment: Optional[str] = None):
doc: Optional[str] = None):
return cls(ctype_name, TupleTypeNode(ctype_name, items),
export_name, comment)
export_name, doc)

@classmethod
def class_(cls, ctype_name: str, class_name: str,
export_name: Optional[str] = None,
comment: Optional[str] = None):
doc: Optional[str] = None):
return cls(ctype_name, ASTNodeTypeNode(class_name),
export_name, comment)
export_name, doc)

@classmethod
def callable_(cls, ctype_name: str,
arg_types: Union[TypeNode, Sequence[TypeNode]],
ret_type: TypeNode = NoneTypeNode("void"),
export_name: Optional[str] = None,
comment: Optional[str] = None):
doc: Optional[str] = None):
return cls(ctype_name,
CallableTypeNode(ctype_name, arg_types, ret_type),
export_name, comment)
export_name, doc)

@classmethod
def ref_(cls, ctype_name: str, alias_ctype_name: str,
alias_export_name: Optional[str] = None,
export_name: Optional[str] = None, comment: Optional[str] = None):
export_name: Optional[str] = None, doc: Optional[str] = None):
return cls(ctype_name,
AliasRefTypeNode(alias_ctype_name, alias_export_name),
export_name, comment)
export_name, doc)

@classmethod
def dict_(cls, ctype_name: str, key_type: TypeNode, value_type: TypeNode,
export_name: Optional[str] = None, comment: Optional[str] = None):
export_name: Optional[str] = None, doc: Optional[str] = None):
return cls(ctype_name, DictTypeNode(ctype_name, key_type, value_type),
export_name, comment)
export_name, doc)


class NDArrayTypeNode(TypeNode):
Expand Down Expand Up @@ -543,6 +554,16 @@ def relative_typename(self, module: str) -> str:
item.relative_typename(module) for item in self
))

@property
def required_definition_imports(self) -> Generator[str, None, None]:
yield "import typing"
return 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
@abc.abstractproperty
def type_format(self) -> str:
pass
Expand All @@ -560,30 +581,22 @@ def __init__(self, ctype_name: str, item: TypeNode) -> None:
super().__init__(ctype_name, (item, ))

@property
def type_format(self):
def type_format(self) -> str:
return "typing.Sequence[{}]"

@property
def types_separator(self):
def types_separator(self) -> str:
return ", "

@property
def required_definition_imports(self) -> Generator[str, None, None]:
yield "import typing"
yield from super().required_definition_imports

@property
def required_usage_imports(self) -> Generator[str, None, None]:
yield "import typing"
yield from super().required_usage_imports


class TupleTypeNode(ContainerTypeNode):
"""Type node representing possibly heterogenous collection of types with
"""Type node representing possibly heterogeneous collection of types with
possibly unspecified length.
"""
@property
def type_format(self):
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Tuple[{}]"
return "tuple[{}]"

@property
Expand All @@ -595,20 +608,34 @@ class UnionTypeNode(ContainerTypeNode):
"""Type node representing type that can be one of the predefined set of types.
"""
@property
def type_format(self):
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Union[{}]"
return "{}"

@property
def types_separator(self):
def types_separator(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return ", "
return " | "


class OptionalTypeNode(UnionTypeNode):
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) -> None:
super().__init__(value.ctype_name, (value, NoneTypeNode(value.ctype_name)))
super().__init__(value.ctype_name, (value,))

@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):
Expand All @@ -627,11 +654,13 @@ def value_type(self) -> TypeNode:
return self.items[1]

@property
def type_format(self):
def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Dict[{}]"
return "dict[{}]"

@property
def types_separator(self):
def types_separator(self) -> str:
return ", "


Expand Down
54 changes: 28 additions & 26 deletions modules/python/src2/typing_stubs_generation/predefined_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,65 +40,65 @@
),
AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()),
AliasTypeNode.sequence_("Size", PrimitiveTypeN F438 ode.int_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Size2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Scalar", PrimitiveTypeNode.float_(),
comment="Required length is at most 4"),
doc="Required length is at most 4"),
AliasTypeNode.sequence_("Point", PrimitiveTypeNode.int_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.ref_("Point2i", "Point"),
AliasTypeNode.sequence_("Point2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Point2d", PrimitiveTypeNode.float_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Point3i", PrimitiveTypeNode.int_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Point3f", PrimitiveTypeNode.float_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Point3d", PrimitiveTypeNode.float_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Range", PrimitiveTypeNode.int_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Rect", PrimitiveTypeNode.int_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2i", PrimitiveTypeNode.int_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2d", PrimitiveTypeNode.float_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.dict_("Moments", PrimitiveTypeNode.str_("Moments::key"),
PrimitiveTypeNode.float_("Moments::value")),
AliasTypeNode.tuple_("RotatedRect",
items=(AliasRefTypeNode("Point2f"),
AliasRefTypeNode("Size"),
PrimitiveTypeNode.float_()),
comment="Any type providing sequence protocol is supported"),
doc="Any type providing sequence protocol is supported"),
AliasTypeNode.tuple_("TermCriteria",
items=(
ASTNodeTypeNode("TermCriteria.Type"),
PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_()),
comment="Any type providing sequence protocol is supported"),
doc="Any type providing sequence protocol is supported"),
AliasTypeNode.sequence_("Vec2i", PrimitiveTypeNode.int_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2d", PrimitiveTypeNode.float_(),
comment="Required length is 2"),
doc="Required length is 2"),
AliasTypeNode.sequence_("Vec3i", PrimitiveTypeNode.int_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3f", PrimitiveTypeNode.float_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3d", PrimitiveTypeNode.float_(),
comment="Required length is 3"),
doc="Required length is 3"),
AliasTypeNode.sequence_("Vec4i", PrimitiveTypeNode.int_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4f", PrimitiveTypeNode.float_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4d", PrimitiveTypeNode.float_(),
comment="Required length is 4"),
doc="Required length is 4"),
AliasTypeNode.sequence_("Vec6f", PrimitiveTypeNode.float_(),
comment="Required length is 6"),
doc="Required length is 6"),
AliasTypeNode.class_("FeatureDetector", "Feature2D",
export_name="FeatureDetector"),
AliasTypeNode.class_("DescriptorExtractor", "Feature2D",
Expand Down Expand Up @@ -202,4 +202,6 @@
PrimitiveTypeNode.float_("map_int_and_double::value")),
)

PREDEFINED_TYPES = dict(zip((t.ctype_name for t in _PREDEFINED_TYPES), _PREDEFINED_TYPES))
PREDEFINED_TYPES = dict(
zip((t.ctype_name for t in _PREDEFINED_TYPES), _PREDEFINED_TYPES)
)
0