88 lines
2.5 KiB
Python
88 lines
2.5 KiB
Python
# SPDX-License-Identifier: MIT
|
|
|
|
import inspect
|
|
import platform
|
|
import sys
|
|
import threading
|
|
|
|
from collections.abc import Mapping, Sequence # noqa: F401
|
|
from typing import _GenericAlias
|
|
|
|
|
|
PYPY = platform.python_implementation() == "PyPy"
|
|
PY_3_8_PLUS = sys.version_info[:2] >= (3, 8)
|
|
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
|
|
PY310 = sys.version_info[:2] >= (3, 10)
|
|
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
|
|
|
|
|
|
if sys.version_info < (3, 8):
|
|
try:
|
|
from typing_extensions import Protocol
|
|
except ImportError: # pragma: no cover
|
|
Protocol = object
|
|
else:
|
|
from typing import Protocol # noqa: F401
|
|
|
|
|
|
class _AnnotationExtractor:
|
|
"""
|
|
Extract type annotations from a callable, returning None whenever there
|
|
is none.
|
|
"""
|
|
|
|
__slots__ = ["sig"]
|
|
|
|
def __init__(self, callable):
|
|
try:
|
|
self.sig = inspect.signature(callable)
|
|
except (ValueError, TypeError): # inspect failed
|
|
self.sig = None
|
|
|
|
def get_first_param_type(self):
|
|
"""
|
|
Return the type annotation of the first argument if it's not empty.
|
|
"""
|
|
if not self.sig:
|
|
return None
|
|
|
|
params = list(self.sig.parameters.values())
|
|
if params and params[0].annotation is not inspect.Parameter.empty:
|
|
return params[0].annotation
|
|
|
|
return None
|
|
|
|
def get_return_type(self):
|
|
"""
|
|
Return the return type if it's not empty.
|
|
"""
|
|
if (
|
|
self.sig
|
|
and self.sig.return_annotation is not inspect.Signature.empty
|
|
):
|
|
return self.sig.return_annotation
|
|
|
|
return None
|
|
|
|
|
|
# Thread-local global to track attrs instances which are already being repr'd.
|
|
# This is needed because there is no other (thread-safe) way to pass info
|
|
# about the instances that are already being repr'd through the call stack
|
|
# in order to ensure we don't perform infinite recursion.
|
|
#
|
|
# For instance, if an instance contains a dict which contains that instance,
|
|
# we need to know that we're already repr'ing the outside instance from within
|
|
# the dict's repr() call.
|
|
#
|
|
# This lives here rather than in _make.py so that the functions in _make.py
|
|
# don't have a direct reference to the thread-local in their globals dict.
|
|
# If they have such a reference, it breaks cloudpickle.
|
|
repr_context = threading.local()
|
|
|
|
|
|
def get_generic_base(cl):
|
|
"""If this is a generic class (A[str]), return the generic base for it."""
|
|
if cl.__class__ is _GenericAlias:
|
|
return cl.__origin__
|
|
return None
|