inspection.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # inspection.py
  2. # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. """The inspection module provides the :func:`_sa.inspect` function,
  8. which delivers runtime information about a wide variety
  9. of SQLAlchemy objects, both within the Core as well as the
  10. ORM.
  11. The :func:`_sa.inspect` function is the entry point to SQLAlchemy's
  12. public API for viewing the configuration and construction
  13. of in-memory objects. Depending on the type of object
  14. passed to :func:`_sa.inspect`, the return value will either be
  15. a related object which provides a known interface, or in many
  16. cases it will return the object itself.
  17. The rationale for :func:`_sa.inspect` is twofold. One is that
  18. it replaces the need to be aware of a large variety of "information
  19. getting" functions in SQLAlchemy, such as
  20. :meth:`_reflection.Inspector.from_engine` (deprecated in 1.4),
  21. :func:`.orm.attributes.instance_state`, :func:`_orm.class_mapper`,
  22. and others. The other is that the return value of :func:`_sa.inspect`
  23. is guaranteed to obey a documented API, thus allowing third party
  24. tools which build on top of SQLAlchemy configurations to be constructed
  25. in a forwards-compatible way.
  26. """
  27. from __future__ import annotations
  28. from typing import Any
  29. from typing import Callable
  30. from typing import Dict
  31. from typing import Generic
  32. from typing import Optional
  33. from typing import overload
  34. from typing import Type
  35. from typing import TypeVar
  36. from typing import Union
  37. from . import exc
  38. from .util.typing import Literal
  39. from .util.typing import Protocol
  40. _T = TypeVar("_T", bound=Any)
  41. _TCov = TypeVar("_TCov", bound=Any, covariant=True)
  42. _F = TypeVar("_F", bound=Callable[..., Any])
  43. _IN = TypeVar("_IN", bound=Any)
  44. _registrars: Dict[type, Union[Literal[True], Callable[[Any], Any]]] = {}
  45. class Inspectable(Generic[_T]):
  46. """define a class as inspectable.
  47. This allows typing to set up a linkage between an object that
  48. can be inspected and the type of inspection it returns.
  49. Unfortunately we cannot at the moment get all classes that are
  50. returned by inspection to suit this interface as we get into
  51. MRO issues.
  52. """
  53. __slots__ = ()
  54. class _InspectableTypeProtocol(Protocol[_TCov]):
  55. """a protocol defining a method that's used when a type (ie the class
  56. itself) is passed to inspect().
  57. """
  58. def _sa_inspect_type(self) -> _TCov: ...
  59. class _InspectableProtocol(Protocol[_TCov]):
  60. """a protocol defining a method that's used when an instance is
  61. passed to inspect().
  62. """
  63. def _sa_inspect_instance(self) -> _TCov: ...
  64. @overload
  65. def inspect(
  66. subject: Type[_InspectableTypeProtocol[_IN]], raiseerr: bool = True
  67. ) -> _IN: ...
  68. @overload
  69. def inspect(
  70. subject: _InspectableProtocol[_IN], raiseerr: bool = True
  71. ) -> _IN: ...
  72. @overload
  73. def inspect(subject: Inspectable[_IN], raiseerr: bool = True) -> _IN: ...
  74. @overload
  75. def inspect(subject: Any, raiseerr: Literal[False] = ...) -> Optional[Any]: ...
  76. @overload
  77. def inspect(subject: Any, raiseerr: bool = True) -> Any: ...
  78. def inspect(subject: Any, raiseerr: bool = True) -> Any:
  79. """Produce an inspection object for the given target.
  80. The returned value in some cases may be the
  81. same object as the one given, such as if a
  82. :class:`_orm.Mapper` object is passed. In other
  83. cases, it will be an instance of the registered
  84. inspection type for the given object, such as
  85. if an :class:`_engine.Engine` is passed, an
  86. :class:`_reflection.Inspector` object is returned.
  87. :param subject: the subject to be inspected.
  88. :param raiseerr: When ``True``, if the given subject
  89. does not
  90. correspond to a known SQLAlchemy inspected type,
  91. :class:`sqlalchemy.exc.NoInspectionAvailable`
  92. is raised. If ``False``, ``None`` is returned.
  93. """
  94. type_ = type(subject)
  95. for cls in type_.__mro__:
  96. if cls in _registrars:
  97. reg = _registrars.get(cls, None)
  98. if reg is None:
  99. continue
  100. elif reg is True:
  101. return subject
  102. ret = reg(subject)
  103. if ret is not None:
  104. return ret
  105. else:
  106. reg = ret = None
  107. if raiseerr and (reg is None or ret is None):
  108. raise exc.NoInspectionAvailable(
  109. "No inspection system is "
  110. "available for object of type %s" % type_
  111. )
  112. return ret
  113. def _inspects(
  114. *types: Type[Any],
  115. ) -> Callable[[_F], _F]:
  116. def decorate(fn_or_cls: _F) -> _F:
  117. for type_ in types:
  118. if type_ in _registrars:
  119. raise AssertionError("Type %s is already registered" % type_)
  120. _registrars[type_] = fn_or_cls
  121. return fn_or_cls
  122. return decorate
  123. _TT = TypeVar("_TT", bound="Type[Any]")
  124. def _self_inspects(cls: _TT) -> _TT:
  125. if cls in _registrars:
  126. raise AssertionError("Type %s is already registered" % cls)
  127. _registrars[cls] = True
  128. return cls