inspect.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from inspect import (
  2. isclass,
  3. ismethod,
  4. isfunction,
  5. isgeneratorfunction,
  6. isgenerator,
  7. iscoroutinefunction,
  8. iscoroutine,
  9. isasyncgenfunction,
  10. isasyncgen,
  11. )
  12. from typing import Any, List
  13. from .undefined import Undefined
  14. __all__ = ["inspect"]
  15. max_recursive_depth = 2
  16. max_str_size = 240
  17. max_list_size = 10
  18. def inspect(value: Any) -> str:
  19. """Inspect value and a return string representation for error messages.
  20. Used to print values in error messages. We do not use repr() in order to not
  21. leak too much of the inner Python representation of unknown objects, and we
  22. do not use json.dumps() because not all objects can be serialized as JSON and
  23. we want to output strings with single quotes like Python repr() does it.
  24. We also restrict the size of the representation by truncating strings and
  25. collections and allowing only a maximum recursion depth.
  26. """
  27. return inspect_recursive(value, [])
  28. def inspect_recursive(value: Any, seen_values: List) -> str:
  29. if value is None or value is Undefined or isinstance(value, (bool, float, complex)):
  30. return repr(value)
  31. if isinstance(value, (int, str, bytes, bytearray)):
  32. return trunc_str(repr(value))
  33. if len(seen_values) < max_recursive_depth and value not in seen_values:
  34. # check if we have a custom inspect method
  35. inspect_method = getattr(value, "__inspect__", None)
  36. if inspect_method is not None and callable(inspect_method):
  37. s = inspect_method()
  38. if isinstance(s, str):
  39. return trunc_str(s)
  40. seen_values = [*seen_values, value]
  41. return inspect_recursive(s, seen_values)
  42. # recursively inspect collections
  43. if isinstance(value, (list, tuple, dict, set, frozenset)):
  44. if not value:
  45. return repr(value)
  46. seen_values = [*seen_values, value]
  47. if isinstance(value, list):
  48. items = value
  49. elif isinstance(value, dict):
  50. items = list(value.items())
  51. else:
  52. items = list(value)
  53. items = trunc_list(items)
  54. if isinstance(value, dict):
  55. s = ", ".join(
  56. "..."
  57. if v is ELLIPSIS
  58. else inspect_recursive(v[0], seen_values)
  59. + ": "
  60. + inspect_recursive(v[1], seen_values)
  61. for v in items
  62. )
  63. else:
  64. s = ", ".join(
  65. "..." if v is ELLIPSIS else inspect_recursive(v, seen_values)
  66. for v in items
  67. )
  68. if isinstance(value, tuple):
  69. if len(items) == 1:
  70. return f"({s},)"
  71. return f"({s})"
  72. if isinstance(value, (dict, set)):
  73. return "{" + s + "}"
  74. if isinstance(value, frozenset):
  75. return f"frozenset({{{s}}})"
  76. return f"[{s}]"
  77. else:
  78. # handle collections that are nested too deep
  79. if isinstance(value, (list, tuple, dict, set, frozenset)):
  80. if not value:
  81. return repr(value)
  82. if isinstance(value, list):
  83. return "[...]"
  84. if isinstance(value, tuple):
  85. return "(...)"
  86. if isinstance(value, dict):
  87. return "{...}"
  88. if isinstance(value, set):
  89. return "set(...)"
  90. return "frozenset(...)"
  91. if isinstance(value, Exception):
  92. type_ = "exception"
  93. value = type(value)
  94. elif isclass(value):
  95. type_ = "exception class" if issubclass(value, Exception) else "class"
  96. elif ismethod(value):
  97. type_ = "method"
  98. elif iscoroutinefunction(value):
  99. type_ = "coroutine function"
  100. elif isasyncgenfunction(value):
  101. type_ = "async generator function"
  102. elif isgeneratorfunction(value):
  103. type_ = "generator function"
  104. elif isfunction(value):
  105. type_ = "function"
  106. elif iscoroutine(value):
  107. type_ = "coroutine"
  108. elif isasyncgen(value):
  109. type_ = "async generator"
  110. elif isgenerator(value):
  111. type_ = "generator"
  112. else:
  113. # stringify (only) the well-known GraphQL types
  114. from ..type import (
  115. GraphQLDirective,
  116. GraphQLNamedType,
  117. GraphQLScalarType,
  118. GraphQLWrappingType,
  119. )
  120. if isinstance(
  121. value,
  122. (
  123. GraphQLDirective,
  124. GraphQLNamedType,
  125. GraphQLScalarType,
  126. GraphQLWrappingType,
  127. ),
  128. ):
  129. return str(value)
  130. try:
  131. name = type(value).__name__
  132. if not name or "<" in name or ">" in name:
  133. raise AttributeError
  134. except AttributeError:
  135. return "<object>"
  136. else:
  137. return f"<{name} instance>"
  138. try:
  139. name = value.__name__
  140. if not name or "<" in name or ">" in name:
  141. raise AttributeError
  142. except AttributeError:
  143. return f"<{type_}>"
  144. else:
  145. return f"<{type_} {name}>"
  146. def trunc_str(s: str) -> str:
  147. """Truncate strings to maximum length."""
  148. if len(s) > max_str_size:
  149. i = max(0, (max_str_size - 3) // 2)
  150. j = max(0, max_str_size - 3 - i)
  151. s = s[:i] + "..." + s[-j:]
  152. return s
  153. def trunc_list(s: List) -> List:
  154. """Truncate lists to maximum length."""
  155. if len(s) > max_list_size:
  156. i = max_list_size // 2
  157. j = i - 1
  158. s = s[:i] + [ELLIPSIS] + s[-j:]
  159. return s
  160. class InspectEllipsisType:
  161. """Singleton class for indicating ellipses in iterables."""
  162. ELLIPSIS = InspectEllipsisType()