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