123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- from inspect import (
- isclass,
- ismethod,
- isfunction,
- isgeneratorfunction,
- isgenerator,
- iscoroutinefunction,
- iscoroutine,
- isasyncgenfunction,
- isasyncgen,
- )
- from typing import Any, List
- from .undefined import Undefined
- __all__ = ["inspect"]
- max_recursive_depth = 2
- max_str_size = 240
- max_list_size = 10
- def inspect(value: Any) -> str:
- """Inspect value and a return string representation for error messages.
- Used to print values in error messages. We do not use repr() in order to not
- leak too much of the inner Python representation of unknown objects, and we
- do not use json.dumps() because not all objects can be serialized as JSON and
- we want to output strings with single quotes like Python repr() does it.
- We also restrict the size of the representation by truncating strings and
- collections and allowing only a maximum recursion depth.
- """
- return inspect_recursive(value, [])
- def inspect_recursive(value: Any, seen_values: List) -> str:
- if value is None or value is Undefined or isinstance(value, (bool, float, complex)):
- return repr(value)
- if isinstance(value, (int, str, bytes, bytearray)):
- return trunc_str(repr(value))
- if len(seen_values) < max_recursive_depth and value not in seen_values:
- # check if we have a custom inspect method
- inspect_method = getattr(value, "__inspect__", None)
- if inspect_method is not None and callable(inspect_method):
- s = inspect_method()
- if isinstance(s, str):
- return trunc_str(s)
- seen_values = [*seen_values, value]
- return inspect_recursive(s, seen_values)
- # recursively inspect collections
- if isinstance(value, (list, tuple, dict, set, frozenset)):
- if not value:
- return repr(value)
- seen_values = [*seen_values, value]
- if isinstance(value, list):
- items = value
- elif isinstance(value, dict):
- items = list(value.items())
- else:
- items = list(value)
- items = trunc_list(items)
- if isinstance(value, dict):
- s = ", ".join(
- "..."
- if v is ELLIPSIS
- else inspect_recursive(v[0], seen_values)
- + ": "
- + inspect_recursive(v[1], seen_values)
- for v in items
- )
- else:
- s = ", ".join(
- "..." if v is ELLIPSIS else inspect_recursive(v, seen_values)
- for v in items
- )
- if isinstance(value, tuple):
- if len(items) == 1:
- return f"({s},)"
- return f"({s})"
- if isinstance(value, (dict, set)):
- return "{" + s + "}"
- if isinstance(value, frozenset):
- return f"frozenset({{{s}}})"
- return f"[{s}]"
- else:
- # handle collections that are nested too deep
- if isinstance(value, (list, tuple, dict, set, frozenset)):
- if not value:
- return repr(value)
- if isinstance(value, list):
- return "[...]"
- if isinstance(value, tuple):
- return "(...)"
- if isinstance(value, dict):
- return "{...}"
- if isinstance(value, set):
- return "set(...)"
- return "frozenset(...)"
- if isinstance(value, Exception):
- type_ = "exception"
- value = type(value)
- elif isclass(value):
- type_ = "exception class" if issubclass(value, Exception) else "class"
- elif ismethod(value):
- type_ = "method"
- elif iscoroutinefunction(value):
- type_ = "coroutine function"
- elif isasyncgenfunction(value):
- type_ = "async generator function"
- elif isgeneratorfunction(value):
- type_ = "generator function"
- elif isfunction(value):
- type_ = "function"
- elif iscoroutine(value):
- type_ = "coroutine"
- elif isasyncgen(value):
- type_ = "async generator"
- elif isgenerator(value):
- type_ = "generator"
- else:
- # stringify (only) the well-known GraphQL types
- from ..type import (
- GraphQLDirective,
- GraphQLNamedType,
- GraphQLScalarType,
- GraphQLWrappingType,
- )
- if isinstance(
- value,
- (
- GraphQLDirective,
- GraphQLNamedType,
- GraphQLScalarType,
- GraphQLWrappingType,
- ),
- ):
- return str(value)
- try:
- name = type(value).__name__
- if not name or "<" in name or ">" in name:
- raise AttributeError
- except AttributeError:
- return "<object>"
- else:
- return f"<{name} instance>"
- try:
- name = value.__name__
- if not name or "<" in name or ">" in name:
- raise AttributeError
- except AttributeError:
- return f"<{type_}>"
- else:
- return f"<{type_} {name}>"
- def trunc_str(s: str) -> str:
- """Truncate strings to maximum length."""
- if len(s) > max_str_size:
- i = max(0, (max_str_size - 3) // 2)
- j = max(0, max_str_size - 3 - i)
- s = s[:i] + "..." + s[-j:]
- return s
- def trunc_list(s: List) -> List:
- """Truncate lists to maximum length."""
- if len(s) > max_list_size:
- i = max_list_size // 2
- j = i - 1
- s = s[:i] + [ELLIPSIS] + s[-j:]
- return s
- class InspectEllipsisType:
- """Singleton class for indicating ellipses in iterables."""
- ELLIPSIS = InspectEllipsisType()
|