Merge pull request #23798 from VadimLevin:dev/vlevin/runtime-typing-module

feat: provide cv2.typing aliases at runtime
This commit is contained in:
Alexander Smorkalov 2023-06-15 14:41:13 +03:00 committed by GitHub
commit 0dde3b65d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 79 deletions

View File

@ -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: def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
assert isinstance(type_node, AggregatedTypeNode), \ assert isinstance(type_node, AggregatedTypeNode), \
"Provided type node '{}' is not an aggregated type".format( f"Provided type node '{type_node.ctype_name}' is not an aggregated type"
type_node.ctype_name
)
for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node): for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node):
register_alias(PREDEFINED_TYPES[item.ctype_name]) # type: ignore register_alias(PREDEFINED_TYPES[item.ctype_name]) # type: ignore
@ -631,8 +629,8 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
aliases[typename] = alias_node.value.full_typename.replace( aliases[typename] = alias_node.value.full_typename.replace(
root.export_name + ".typing.", "" root.export_name + ".typing.", ""
) )
if alias_node.comment is not None: if alias_node.doc is not None:
aliases[typename] += " # " + alias_node.comment aliases[typename] += f'\n"""{alias_node.doc}"""'
for required_import in alias_node.required_definition_imports: for required_import in alias_node.required_definition_imports:
required_imports.add(required_import) required_imports.add(required_import)
@ -643,12 +641,18 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
aliases: Dict[str, str] = {} aliases: Dict[str, str] = {}
# Resolve each node and register aliases # Resolve each node and register aliases
TypeNode.compatible_to_runtime_usage = True
for node in PREDEFINED_TYPES.values(): for node in PREDEFINED_TYPES.values():
node.resolve(root) node.resolve(root)
if isinstance(node, AliasTypeNode): if isinstance(node, AliasTypeNode):
register_alias(node) register_alias(node)
output_stream = StringIO() 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) _write_required_imports(required_imports, output_stream)
for alias_name, alias_type in aliases.items(): for alias_name, alias_type in aliases.items():
@ -657,7 +661,8 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
output_stream.write(alias_type) output_stream.write(alias_type)
output_stream.write("\n") 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] StubGenerator = Callable[[ASTNode, StringIO, int], None]

View File

@ -19,6 +19,17 @@ class TypeNode(abc.ABC):
e.g. `cv::Rect`. e.g. `cv::Rect`.
- There is no information about types visibility (see `ASTNodeTypeNode`). - 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: def __init__(self, ctype_name: str) -> None:
self.ctype_name = ctype_name self.ctype_name = ctype_name
@ -247,11 +258,11 @@ class AliasTypeNode(TypeNode):
""" """
def __init__(self, ctype_name: str, value: TypeNode, def __init__(self, ctype_name: str, value: TypeNode,
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None) -> None: doc: Optional[str] = None) -> None:
super().__init__(ctype_name) super().__init__(ctype_name)
self.value = value self.value = value
self._export_name = export_name self._export_name = export_name
self.comment = comment self.doc = doc
@property @property
def typename(self) -> str: def typename(self) -> str:
@ -287,82 +298,82 @@ class AliasTypeNode(TypeNode):
@classmethod @classmethod
def int_(cls, ctype_name: str, export_name: Optional[str] = None, def int_(cls, ctype_name: str, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, comment) return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc)
@classmethod @classmethod
def float_(cls, ctype_name: str, export_name: Optional[str] = None, def float_(cls, ctype_name: str, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, comment) return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc)
@classmethod @classmethod
def array_(cls, ctype_name: str, shape: Optional[Tuple[int, ...]], def array_(cls, ctype_name: str, shape: Optional[Tuple[int, ...]],
dtype: Optional[str] = None, export_name: Optional[str] = None, dtype: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
if comment is None: if doc is None:
comment = "Shape: " + str(shape) doc = "Shape: " + str(shape)
else: else:
comment += ". Shape: " + str(shape) doc += ". Shape: " + str(shape)
return cls(ctype_name, NDArrayTypeNode(ctype_name, shape, dtype), return cls(ctype_name, NDArrayTypeNode(ctype_name, shape, dtype),
export_name, comment) export_name, doc)
@classmethod @classmethod
def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...], def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, UnionTypeNode(ctype_name, items), return cls(ctype_name, UnionTypeNode(ctype_name, items),
export_name, comment) export_name, doc)
@classmethod @classmethod
def optional_(cls, ctype_name: str, item: TypeNode, def optional_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, OptionalTypeNode(item), export_name, comment) return cls(ctype_name, OptionalTypeNode(item), export_name, doc)
@classmethod @classmethod
def sequence_(cls, ctype_name: str, item: TypeNode, def sequence_(cls, ctype_name: str, item: TypeNode,
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, SequenceTypeNode(ctype_name, item), return cls(ctype_name, SequenceTypeNode(ctype_name, item),
export_name, comment) export_name, doc)
@classmethod @classmethod
def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...], def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, TupleTypeNode(ctype_name, items), return cls(ctype_name, TupleTypeNode(ctype_name, items),
export_name, comment) export_name, doc)
@classmethod @classmethod
def class_(cls, ctype_name: str, class_name: str, def class_(cls, ctype_name: str, class_name: str,
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, ASTNodeTypeNode(class_name), return cls(ctype_name, ASTNodeTypeNode(class_name),
export_name, comment) export_name, doc)
@classmethod @classmethod
def callable_(cls, ctype_name: str, def callable_(cls, ctype_name: str,
arg_types: Union[TypeNode, Sequence[TypeNode]], arg_types: Union[TypeNode, Sequence[TypeNode]],
ret_type: TypeNode = NoneTypeNode("void"), ret_type: TypeNode = NoneTypeNode("void"),
export_name: Optional[str] = None, export_name: Optional[str] = None,
comment: Optional[str] = None): doc: Optional[str] = None):
return cls(ctype_name, return cls(ctype_name,
CallableTypeNode(ctype_name, arg_types, ret_type), CallableTypeNode(ctype_name, arg_types, ret_type),
export_name, comment) export_name, doc)
@classmethod @classmethod
def ref_(cls, ctype_name: str, alias_ctype_name: str, def ref_(cls, ctype_name: str, alias_ctype_name: str,
alias_export_name: Optional[str] = None, 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, return cls(ctype_name,
AliasRefTypeNode(alias_ctype_name, alias_export_name), AliasRefTypeNode(alias_ctype_name, alias_export_name),
export_name, comment) export_name, doc)
@classmethod @classmethod
def dict_(cls, ctype_name: str, key_type: TypeNode, value_type: TypeNode, 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), return cls(ctype_name, DictTypeNode(ctype_name, key_type, value_type),
export_name, comment) export_name, doc)
class NDArrayTypeNode(TypeNode): class NDArrayTypeNode(TypeNode):
@ -543,6 +554,16 @@ class ContainerTypeNode(AggregatedTypeNode):
item.relative_typename(module) for item in self 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 @abc.abstractproperty
def type_format(self) -> str: def type_format(self) -> str:
pass pass
@ -560,30 +581,22 @@ class SequenceTypeNode(ContainerTypeNode):
super().__init__(ctype_name, (item, )) super().__init__(ctype_name, (item, ))
@property @property
def type_format(self): def type_format(self) -> str:
return "typing.Sequence[{}]" return "typing.Sequence[{}]"
@property @property
def types_separator(self): def types_separator(self) -> str:
return ", " 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): class TupleTypeNode(ContainerTypeNode):
"""Type node representing possibly heterogenous collection of types with """Type node representing possibly heterogeneous collection of types with
possibly unspecified length. possibly unspecified length.
""" """
@property @property
def type_format(self): def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Tuple[{}]"
return "tuple[{}]" return "tuple[{}]"
@property @property
@ -595,20 +608,34 @@ class UnionTypeNode(ContainerTypeNode):
"""Type node representing type that can be one of the predefined set of types. """Type node representing type that can be one of the predefined set of types.
""" """
@property @property
def type_format(self): def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Union[{}]"
return "{}" return "{}"
@property @property
def types_separator(self): def types_separator(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return ", "
return " | " return " | "
class OptionalTypeNode(UnionTypeNode): class OptionalTypeNode(ContainerTypeNode):
"""Type node representing optional type which is effectively is a union """Type node representing optional type which is effectively is a union
of value type node and None. of value type node and None.
""" """
def __init__(self, value: TypeNode) -> 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): class DictTypeNode(ContainerTypeNode):
@ -627,11 +654,13 @@ class DictTypeNode(ContainerTypeNode):
return self.items[1] return self.items[1]
@property @property
def type_format(self): def type_format(self) -> str:
if TypeNode.compatible_to_runtime_usage:
return "typing.Dict[{}]"
return "dict[{}]" return "dict[{}]"
@property @property
def types_separator(self): def types_separator(self) -> str:
return ", " return ", "

View File

@ -40,65 +40,65 @@ _PREDEFINED_TYPES = (
), ),
AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()), AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()),
AliasTypeNode.sequence_("Size", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Size", PrimitiveTypeNode.int_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Size2f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Size2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Scalar", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Scalar", PrimitiveTypeNode.float_(),
comment="Required length is at most 4"), doc="Required length is at most 4"),
AliasTypeNode.sequence_("Point", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Point", PrimitiveTypeNode.int_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.ref_("Point2i", "Point"), AliasTypeNode.ref_("Point2i", "Point"),
AliasTypeNode.sequence_("Point2f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Point2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Point2d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Point2d", PrimitiveTypeNode.float_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Point3i", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Point3i", PrimitiveTypeNode.int_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Point3f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Point3f", PrimitiveTypeNode.float_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Point3d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Point3d", PrimitiveTypeNode.float_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Range", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Range", PrimitiveTypeNode.int_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Rect", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Rect", PrimitiveTypeNode.int_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2i", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Rect2i", PrimitiveTypeNode.int_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.sequence_("Rect2d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Rect2d", PrimitiveTypeNode.float_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.dict_("Moments", PrimitiveTypeNode.str_("Moments::key"), AliasTypeNode.dict_("Moments", PrimitiveTypeNode.str_("Moments::key"),
PrimitiveTypeNode.float_("Moments::value")), PrimitiveTypeNode.float_("Moments::value")),
AliasTypeNode.tuple_("RotatedRect", AliasTypeNode.tuple_("RotatedRect",
items=(AliasRefTypeNode("Point2f"), items=(AliasRefTypeNode("Point2f"),
AliasRefTypeNode("Size"), AliasRefTypeNode("Size"),
PrimitiveTypeNode.float_()), PrimitiveTypeNode.float_()),
comment="Any type providing sequence protocol is supported"), doc="Any type providing sequence protocol is supported"),
AliasTypeNode.tuple_("TermCriteria", AliasTypeNode.tuple_("TermCriteria",
items=( items=(
ASTNodeTypeNode("TermCriteria.Type"), ASTNodeTypeNode("TermCriteria.Type"),
PrimitiveTypeNode.int_(), PrimitiveTypeNode.int_(),
PrimitiveTypeNode.float_()), PrimitiveTypeNode.float_()),
comment="Any type providing sequence protocol is supported"), doc="Any type providing sequence protocol is supported"),
AliasTypeNode.sequence_("Vec2i", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Vec2i", PrimitiveTypeNode.int_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec2f", PrimitiveTypeNode.float_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Vec2d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec2d", PrimitiveTypeNode.float_(),
comment="Required length is 2"), doc="Required length is 2"),
AliasTypeNode.sequence_("Vec3i", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Vec3i", PrimitiveTypeNode.int_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec3f", PrimitiveTypeNode.float_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Vec3d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec3d", PrimitiveTypeNode.float_(),
comment="Required length is 3"), doc="Required length is 3"),
AliasTypeNode.sequence_("Vec4i", PrimitiveTypeNode.int_(), AliasTypeNode.sequence_("Vec4i", PrimitiveTypeNode.int_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec4f", PrimitiveTypeNode.float_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.sequence_("Vec4d", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec4d", PrimitiveTypeNode.float_(),
comment="Required length is 4"), doc="Required length is 4"),
AliasTypeNode.sequence_("Vec6f", PrimitiveTypeNode.float_(), AliasTypeNode.sequence_("Vec6f", PrimitiveTypeNode.float_(),
comment="Required length is 6"), doc="Required length is 6"),
AliasTypeNode.class_("FeatureDetector", "Feature2D", AliasTypeNode.class_("FeatureDetector", "Feature2D",
export_name="FeatureDetector"), export_name="FeatureDetector"),
AliasTypeNode.class_("DescriptorExtractor", "Feature2D", AliasTypeNode.class_("DescriptorExtractor", "Feature2D",
@ -202,4 +202,6 @@ _PREDEFINED_TYPES = (
PrimitiveTypeNode.float_("map_int_and_double::value")), 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)
)