Skip to content

Commit e4ae2e5

Browse files
authored
Fix incosistent value represntation (#132)
1 parent e2fd506 commit e4ae2e5

File tree

20 files changed

+144
-96
lines changed

20 files changed

+144
-96
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,4 @@ cython_debug/
210210

211211
tmp
212212
*.patch
213+
*.zip

pybind11_stubgen/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
IgnoreInvalidIdentifierErrors,
1414
IgnoreUnresolvedNameErrors,
1515
LogErrors,
16+
LoggerData,
1617
SuggestCxxSignatureFix,
1718
TerminateOnFatalErrors,
1819
)
1920
from pybind11_stubgen.parser.mixins.filter import (
2021
FilterClassMembers,
2122
FilterInvalidIdentifiers,
2223
FilterPybindInternals,
24+
FilterTypingModuleAttributes,
2325
)
2426
from pybind11_stubgen.parser.mixins.fix import (
2527
FixBuiltinTypes,
@@ -157,6 +159,7 @@ def regex(pattern_str: str) -> re.Pattern:
157159

158160
def stub_parser_from_args(args) -> IParser:
159161
error_handlers_top: list[type] = [
162+
LoggerData,
160163
*([IgnoreAllErrors] if args.ignore_all_errors else []),
161164
*([IgnoreInvalidIdentifierErrors] if args.ignore_invalid_identifiers else []),
162165
*([IgnoreInvalidExpressionErrors] if args.ignore_invalid_expressions else []),
@@ -183,6 +186,7 @@ class Parser(
183186
FixMissing__all__Attribute,
184187
FixMissingNoneHashFieldAnnotation,
185188
FixMissingImports,
189+
FilterTypingModuleAttributes,
186190
FixPEP585CollectionNames,
187191
FixTypingTypeNames,
188192
FixTypingExtTypeNames,

pybind11_stubgen/parser/interface.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@
2525

2626

2727
class IParser(abc.ABC):
28-
@abc.abstractmethod
29-
def is_print_safe(self, value: Value) -> bool:
30-
...
31-
3228
@abc.abstractmethod
3329
def handle_alias(self, path: QualifiedName, origin: Any) -> Alias | None:
3430
...
@@ -113,10 +109,6 @@ def parse_annotation_str(
113109
def parse_value_str(self, value: str) -> Value:
114110
...
115111

116-
@abc.abstractmethod
117-
def value_to_repr(self, value: Any) -> str:
118-
...
119-
120112
@abc.abstractmethod
121113
def report_error(self, error: ParserError) -> None:
122114
...

pybind11_stubgen/parser/mixins/error_handlers.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,55 @@
1212
ParserError,
1313
)
1414
from pybind11_stubgen.parser.interface import IParser
15-
from pybind11_stubgen.structs import Module, QualifiedName
16-
17-
logger = getLogger("pybind11_stubgen")
15+
from pybind11_stubgen.structs import Class, Module, QualifiedName
1816

1917

20-
class LogErrors(IParser):
18+
class LoggerData(IParser):
2119
def __init__(self):
2220
super().__init__()
2321
self._seen_errors: set[str] = set()
24-
self._module: str | None = None
22+
self.__current_path: QualifiedName | None = None
2523

2624
def handle_module(
2725
self, path: QualifiedName, module: types.ModuleType
2826
) -> Module | None:
2927
old_errors = self._seen_errors
30-
old_module = self._module
28+
old_module = self.__current_path
3129
self._seen_errors = set()
32-
self._module = str(path)
30+
self.__current_path = path
3331
result = super().handle_module(path, module)
3432
self._seen_errors = old_errors
35-
self._module = old_module
33+
self.__current_path = old_module
34+
return result
35+
36+
def handle_class(self, path: QualifiedName, class_: type) -> Class | None:
37+
old_errors = self._seen_errors
38+
old_module = self.__current_path
39+
self._seen_errors = set()
40+
self.__current_path = path
41+
result = super().handle_class(path, class_)
42+
self._seen_errors = old_errors
43+
self.__current_path = old_module
3644
return result
3745

38-
def report_error(self, error: ParserError) -> None:
39-
error_str = f"In {self._module} : {error}"
40-
if error_str not in self._seen_errors:
46+
@property
47+
def current_path(self) -> QualifiedName:
48+
return self.__current_path
49+
50+
@property
51+
def reported_errors(self) -> set[str]:
52+
return self._seen_errors
53+
54+
55+
logger = getLogger("pybind11_stubgen")
56+
57+
58+
class LogErrors(IParser):
59+
def report_error(self: LoggerData, error: ParserError) -> None:
60+
error_str = f"In {self.current_path} : {error}"
61+
if error_str not in self.reported_errors:
4162
logger.error(error_str)
42-
self._seen_errors.add(error_str)
63+
self.reported_errors.add(error_str)
4364
super().report_error(error)
4465

4566

pybind11_stubgen/parser/mixins/filter.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import types
4+
import typing
45
from typing import Any
56

67
from pybind11_stubgen.parser.errors import InvalidIdentifierError
@@ -21,19 +22,29 @@
2122
)
2223

2324

25+
class FilterTypingModuleAttributes(IParser):
26+
__sentinel = object()
27+
28+
def handle_attribute(self, path: QualifiedName, attr: Any) -> Attribute | None:
29+
if getattr(typing, str(path[-1]), self.__sentinel) is attr:
30+
return None
31+
return super().handle_attribute(path, attr)
32+
33+
2434
class FilterClassMembers(IParser):
2535
__attribute_blacklist: set[Identifier] = {
2636
*map(
2737
Identifier,
2838
(
39+
"__annotations__",
40+
"__builtins__",
41+
"__cached__",
2942
"__file__",
3043
"__loader__",
3144
"__name__",
3245
"__package__",
33-
"__spec__",
3446
"__path__",
35-
"__cached__",
36-
"__builtins__",
47+
"__spec__",
3748
),
3849
)
3950
}

pybind11_stubgen/parser/mixins/fix.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,10 @@ class FixValueReprRandomAddress(IParser):
451451
r"(?P<address>0x[a-fA-F0-9]+)>"
452452
)
453453

454-
def value_to_repr(self, value: Any) -> str:
455-
result = super().value_to_repr(value)
456-
return self._pattern.sub(r"<\g<name> object>", result)
454+
def handle_value(self, value: Any) -> Value:
455+
result = super().handle_value(value)
456+
result.repr = self._pattern.sub(r"<\g<name> object>", result.repr)
457+
return result
457458

458459

459460
class FixNumpyArrayDimAnnotation(IParser):

pybind11_stubgen/parser/mixins/parse.py

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -157,44 +157,6 @@ def _is_descriptor(self, member: Any) -> bool:
157157

158158

159159
class BaseParser(IParser):
160-
def is_print_safe(self, value: Any) -> bool:
161-
value_type = type(value)
162-
# Use exact type match, not `isinstance()` that allows inherited types pass
163-
if value is None or value_type in (int, str):
164-
return True
165-
if value_type in (float, complex):
166-
try:
167-
# checks for NaN, +inf, -inf
168-
eval(repr(value))
169-
return True
170-
except (SyntaxError, NameError):
171-
return False
172-
if value_type in (list, tuple, set):
173-
assert isinstance(value, (list, tuple, set))
174-
for x in value:
175-
if not self.is_print_safe(x):
176-
return False
177-
return True
178-
if value_type is dict:
179-
assert isinstance(value, dict)
180-
for k, v in value.items():
181-
if not self.is_print_safe(k) or not self.is_print_safe(v):
182-
return False
183-
return True
184-
if inspect.isfunction(value):
185-
module_name = getattr(value, "__module__", None)
186-
qual_name = getattr(value, "__qualname__", None)
187-
if (
188-
module_name is not None
189-
and "<" not in module_name
190-
and qual_name is not None
191-
and "<" not in qual_name
192-
):
193-
return True
194-
if inspect.ismodule(value):
195-
return True
196-
return False
197-
198160
def handle_alias(self, path: QualifiedName, origin: Any) -> Alias | None:
199161
full_name = self._get_full_name(path, origin)
200162
if full_name is None:
@@ -345,10 +307,67 @@ def handle_method(self, path: QualifiedName, method: Any) -> list[Method]:
345307
]
346308

347309
def handle_value(self, value: Any) -> Value:
348-
result = self.parse_value_str(self.value_to_repr(value))
349-
if self.is_print_safe(value):
350-
result.is_print_safe = True
351-
return result
310+
value_type = type(value)
311+
# Use exact type match, not `isinstance()` that allows inherited types pass
312+
if value is None or value_type in (bool, int, str):
313+
return Value(repr=repr(value), is_print_safe=True)
314+
if value_type in (float, complex):
315+
try:
316+
# checks for NaN, +inf, -inf
317+
repr_str = repr(value)
318+
eval(repr_str)
319+
return Value(repr=repr_str, is_print_safe=True)
320+
except (SyntaxError, NameError):
321+
pass
322+
if value_type in (list, tuple, set):
323+
assert isinstance(value, (list, tuple, set))
324+
if len(value) == 0:
325+
return Value(repr=f"{value_type.__name__}()", is_print_safe=True)
326+
elements: list[Value] = [self.handle_value(el) for el in value]
327+
is_print_safe = all(el.is_print_safe for el in elements)
328+
left, right = {
329+
list: "[]",
330+
tuple: "()",
331+
set: "{}",
332+
}[value_type]
333+
return Value(
334+
repr="".join([left, ", ".join(el.repr for el in elements), right]),
335+
is_print_safe=is_print_safe,
336+
)
337+
if value_type is dict:
338+
assert isinstance(value, dict)
339+
parts = []
340+
is_print_safe = True
341+
for k, v in value.items():
342+
k_value = self.handle_value(k)
343+
v_value = self.handle_value(v)
344+
parts.append(f"{k_value.repr}: {v_value.repr}")
345+
is_print_safe = (
346+
is_print_safe and k_value.is_print_safe and v_value.is_print_safe
347+
)
348+
349+
return Value(
350+
repr="".join(["{", ", ".join(parts), "}"]), is_print_safe=is_print_safe
351+
)
352+
if inspect.isroutine(value):
353+
module_name = getattr(value, "__module__", None)
354+
qual_name = getattr(value, "__qualname__", None)
355+
if (
356+
module_name is not None
357+
and "<" not in module_name
358+
and qual_name is not None
359+
and "<" not in qual_name
360+
):
361+
if module_name == "builtins":
362+
repr_str = qual_name
363+
else:
364+
repr_str = f"{module_name}.{qual_name}"
365+
return Value(repr=repr_str, is_print_safe=True)
366+
if inspect.isclass(value):
367+
return Value(repr=str(self.handle_type(value)), is_print_safe=True)
368+
if inspect.ismodule(value):
369+
return Value(repr=value.__name__, is_print_safe=True)
370+
return Value(repr=repr(value), is_print_safe=False)
352371

353372
def handle_type(self, type_: type) -> QualifiedName:
354373
return QualifiedName(
@@ -364,22 +383,6 @@ def handle_type(self, type_: type) -> QualifiedName:
364383
def parse_value_str(self, value: str) -> Value:
365384
return Value(value)
366385

367-
def value_to_repr(self, value: Any) -> str:
368-
if inspect.ismodule(value):
369-
return value.__name__
370-
if inspect.isroutine(value):
371-
parts = []
372-
module = getattr(value, "__module__", None)
373-
if module is not None:
374-
parts.append(module)
375-
name = getattr(value, "__qualname__", None)
376-
if name is not None:
377-
parts.append(name)
378-
return ".".join(parts)
379-
if value is ...:
380-
return "..."
381-
return repr(value)
382-
383386
def report_error(self, error: ParserError):
384387
if isinstance(error, NameResolutionError):
385388
if error.name[0] == "module":
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from . import classes, functions
1+
from . import classes, functions, values
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from typing import Callable, Dict
2+
3+
callables_dict: Dict[str, Callable] = {"len": len, "int": int}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
22

3-
from . import classes, functions, functions_3_8_plus, functions_3_9_plus
3+
from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values
44

5-
__all__ = ["classes", "functions", "functions_3_8_plus", "functions_3_9_plus"]
5+
__all__ = ["classes", "functions", "functions_3_8_plus", "functions_3_9_plus", "values"]

0 commit comments

Comments
 (0)