1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
"""Contains a class used to resolve compatibility issues with old Python versions.
Typing stubs generation is available starting from Python 3.6 only.
For other versions all calls to functions are noop.
"""
import sys
import warnings
if sys.version_info >= (3, 6):
from contextlib import contextmanager
from typing import Dict, Set, Any, Sequence, Generator, Union
import traceback
from pathlib import Path
from typing_stubs_generation import (
generate_typing_stubs,
NamespaceNode,
EnumerationNode,
SymbolName,
ClassNode,
create_function_node,
create_class_node,
find_class_node,
resolve_enum_scopes
)
import functools
class FailuresWrapper:
def __init__(self, exceptions_as_warnings=True):
self.has_failure = False
self.exceptions_as_warnings = exceptions_as_warnings
def wrap_exceptions_as_warnings(self, original_func=None,
ret_type_on_failure=None):
def parametrized_wrapper(func):
@functools.wraps(func)
def wrapped_func(*args, **kwargs):
if self.has_failure:
if ret_type_on_failure is None:
return None
return ret_type_on_failure()
try:
ret_type = func(*args, **kwargs)
except Exception:
self.has_failure = True
warnings.warn(
"Typing stubs generation has failed.\n{}".format(
traceback.format_exc()
)
)
if ret_type_on_failure is None:
return None
return ret_type_on_failure()
return ret_type
if self.exceptions_as_warnings:
return wrapped_func
else:
return original_func
if original_func:
return parametrized_wrapper(original_func)
return parametrized_wrapper
@contextmanager
def delete_on_failure(self, file_path):
# type: (Path) -> Generator[None, None, None]
# There is no errors during stubs generation and file doesn't exist
if not self.has_failure and not file_path.is_file():
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.touch()
try:
# continue execution
yield
finally:
# If failure is occurred - delete file if exists
if self.has_failure and file_path.is_file():
file_path.unlink()
failures_wrapper = FailuresWrapper(exceptions_as_warnings=True)
class ClassNodeStub:
def add_base(self, base_node):
pass
class TypingStubsGenerator:
def __init__(self):
self.cv_root = NamespaceNode("cv", export_name="cv2")
self.exported_enums = {} # type: Dict[SymbolName, EnumerationNode]
self.type_hints_ignored_functions = set() # type: Set[str]
@failures_wrapper.wrap_exceptions_as_warnings
def add_enum(self, symbol_name, is_scoped_enum, entries):
# type: (SymbolName, bool, Dict[str, str]) -> None
if symbol_name in self.exported_enums:
assert symbol_name.name == "<unnamed>", \
"Trying to export 2 enums with same symbol " \
"name: {}".format(symbol_name)
enumeration_node = self.exported_enums[symbol_name]
else:
enumeration_node = EnumerationNode(symbol_name.name,
is_scoped_enum)
self.exported_enums[symbol_name] = enumeration_node
for entry_name, entry_value in entries.items():
enumeration_node.add_constant(entry_name, entry_value)
@failures_wrapper.wrap_exceptions_as_warnings
def add_ignored_function_name(self, function_name):
# type: (str) -> None
self.type_hints_ignored_functions.add(function_name)
@failures_wrapper.wrap_exceptions_as_warnings
def create_function_node(self, func_info):
# type: (Any) -> None
create_function_node(self.cv_root, func_info)
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
def find_class_node(self, class_info, namespaces):
# type: (Any, Sequence[str]) -> ClassNode
return find_class_node(
self.cv_root,
SymbolName.parse(class_info.full_original_name, namespaces),
create_missing_namespaces=True
)
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
def create_class_node(self, class_info, namespaces):
# type: (Any, Sequence[str]) -> ClassNode
return create_class_node(self.cv_root, class_info, namespaces)
def generate(self, output_path):
# type: (Union[str, Path]) -> None
output_path = Path(output_path)
py_typed_path = output_path / self.cv_root.export_name / 'py.typed'
with failures_wrapper.delete_on_failure(py_typed_path):
self._generate(output_path)
@failures_wrapper.wrap_exceptions_as_warnings
def _generate(self, output_path):
# type: (Path) -> None
resolve_enum_scopes(self.cv_root, self.exported_enums)
generate_typing_stubs(self.cv_root, output_path)
else:
class ClassNode:
def add_base(self, base_node):
pass
class TypingStubsGenerator:
def __init__(self):
self.type_hints_ignored_functions = set() # type: Set[str]
print(
'WARNING! Typing stubs can be generated only with Python 3.6 or higher. '
'Current version {}'.format(sys.version_info)
)
def add_enum(self, symbol_name, is_scoped_enum, entries):
pass
def add_ignored_function_name(self, function_name):
pass
def create_function_node(self, func_info):
pass
def create_class_node(self, class_info, namespaces):
return ClassNode()
def find_class_node(self, class_info, namespaces):
return ClassNode()
def generate(self, output_path):
pass
|