reflection.py 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102
  1. # engine/reflection.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. """Provides an abstraction for obtaining database schema information.
  8. Usage Notes:
  9. Here are some general conventions when accessing the low level inspector
  10. methods such as get_table_names, get_columns, etc.
  11. 1. Inspector methods return lists of dicts in most cases for the following
  12. reasons:
  13. * They're both standard types that can be serialized.
  14. * Using a dict instead of a tuple allows easy expansion of attributes.
  15. * Using a list for the outer structure maintains order and is easy to work
  16. with (e.g. list comprehension [d['name'] for d in cols]).
  17. 2. Records that contain a name, such as the column name in a column record
  18. use the key 'name'. So for most return values, each record will have a
  19. 'name' attribute..
  20. """
  21. from __future__ import annotations
  22. import contextlib
  23. from dataclasses import dataclass
  24. from enum import auto
  25. from enum import Flag
  26. from enum import unique
  27. from typing import Any
  28. from typing import Callable
  29. from typing import Collection
  30. from typing import Dict
  31. from typing import Generator
  32. from typing import Iterable
  33. from typing import List
  34. from typing import Optional
  35. from typing import Sequence
  36. from typing import Set
  37. from typing import Tuple
  38. from typing import TYPE_CHECKING
  39. from typing import TypeVar
  40. from typing import Union
  41. from .base import Connection
  42. from .base import Engine
  43. from .. import exc
  44. from .. import inspection
  45. from .. import sql
  46. from .. import util
  47. from ..sql import operators
  48. from ..sql import schema as sa_schema
  49. from ..sql.cache_key import _ad_hoc_cache_key_from_args
  50. from ..sql.elements import quoted_name
  51. from ..sql.elements import TextClause
  52. from ..sql.type_api import TypeEngine
  53. from ..sql.visitors import InternalTraversal
  54. from ..util import topological
  55. from ..util.typing import final
  56. if TYPE_CHECKING:
  57. from .interfaces import Dialect
  58. from .interfaces import ReflectedCheckConstraint
  59. from .interfaces import ReflectedColumn
  60. from .interfaces import ReflectedForeignKeyConstraint
  61. from .interfaces import ReflectedIndex
  62. from .interfaces import ReflectedPrimaryKeyConstraint
  63. from .interfaces import ReflectedTableComment
  64. from .interfaces import ReflectedUniqueConstraint
  65. from .interfaces import TableKey
  66. _R = TypeVar("_R")
  67. @util.decorator
  68. def cache(
  69. fn: Callable[..., _R],
  70. self: Dialect,
  71. con: Connection,
  72. *args: Any,
  73. **kw: Any,
  74. ) -> _R:
  75. info_cache = kw.get("info_cache", None)
  76. if info_cache is None:
  77. return fn(self, con, *args, **kw)
  78. exclude = {"info_cache", "unreflectable"}
  79. key = (
  80. fn.__name__,
  81. tuple(
  82. (str(a), a.quote) if isinstance(a, quoted_name) else a
  83. for a in args
  84. if isinstance(a, str)
  85. ),
  86. tuple(
  87. (k, (str(v), v.quote) if isinstance(v, quoted_name) else v)
  88. for k, v in kw.items()
  89. if k not in exclude
  90. ),
  91. )
  92. ret: _R = info_cache.get(key)
  93. if ret is None:
  94. ret = fn(self, con, *args, **kw)
  95. info_cache[key] = ret
  96. return ret
  97. def flexi_cache(
  98. *traverse_args: Tuple[str, InternalTraversal]
  99. ) -> Callable[[Callable[..., _R]], Callable[..., _R]]:
  100. @util.decorator
  101. def go(
  102. fn: Callable[..., _R],
  103. self: Dialect,
  104. con: Connection,
  105. *args: Any,
  106. **kw: Any,
  107. ) -> _R:
  108. info_cache = kw.get("info_cache", None)
  109. if info_cache is None:
  110. return fn(self, con, *args, **kw)
  111. key = _ad_hoc_cache_key_from_args((fn.__name__,), traverse_args, args)
  112. ret: _R = info_cache.get(key)
  113. if ret is None:
  114. ret = fn(self, con, *args, **kw)
  115. info_cache[key] = ret
  116. return ret
  117. return go
  118. @unique
  119. class ObjectKind(Flag):
  120. """Enumerator that indicates which kind of object to return when calling
  121. the ``get_multi`` methods.
  122. This is a Flag enum, so custom combinations can be passed. For example,
  123. to reflect tables and plain views ``ObjectKind.TABLE | ObjectKind.VIEW``
  124. may be used.
  125. .. note::
  126. Not all dialect may support all kind of object. If a dialect does
  127. not support a particular object an empty dict is returned.
  128. In case a dialect supports an object, but the requested method
  129. is not applicable for the specified kind the default value
  130. will be returned for each reflected object. For example reflecting
  131. check constraints of view return a dict with all the views with
  132. empty lists as values.
  133. """
  134. TABLE = auto()
  135. "Reflect table objects"
  136. VIEW = auto()
  137. "Reflect plain view objects"
  138. MATERIALIZED_VIEW = auto()
  139. "Reflect materialized view object"
  140. ANY_VIEW = VIEW | MATERIALIZED_VIEW
  141. "Reflect any kind of view objects"
  142. ANY = TABLE | VIEW | MATERIALIZED_VIEW
  143. "Reflect all type of objects"
  144. @unique
  145. class ObjectScope(Flag):
  146. """Enumerator that indicates which scope to use when calling
  147. the ``get_multi`` methods.
  148. """
  149. DEFAULT = auto()
  150. "Include default scope"
  151. TEMPORARY = auto()
  152. "Include only temp scope"
  153. ANY = DEFAULT | TEMPORARY
  154. "Include both default and temp scope"
  155. @inspection._self_inspects
  156. class Inspector(inspection.Inspectable["Inspector"]):
  157. """Performs database schema inspection.
  158. The Inspector acts as a proxy to the reflection methods of the
  159. :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
  160. consistent interface as well as caching support for previously
  161. fetched metadata.
  162. A :class:`_reflection.Inspector` object is usually created via the
  163. :func:`_sa.inspect` function, which may be passed an
  164. :class:`_engine.Engine`
  165. or a :class:`_engine.Connection`::
  166. from sqlalchemy import inspect, create_engine
  167. engine = create_engine("...")
  168. insp = inspect(engine)
  169. Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated
  170. with the engine may opt to return an :class:`_reflection.Inspector`
  171. subclass that
  172. provides additional methods specific to the dialect's target database.
  173. """
  174. bind: Union[Engine, Connection]
  175. engine: Engine
  176. _op_context_requires_connect: bool
  177. dialect: Dialect
  178. info_cache: Dict[Any, Any]
  179. @util.deprecated(
  180. "1.4",
  181. "The __init__() method on :class:`_reflection.Inspector` "
  182. "is deprecated and "
  183. "will be removed in a future release. Please use the "
  184. ":func:`.sqlalchemy.inspect` "
  185. "function on an :class:`_engine.Engine` or "
  186. ":class:`_engine.Connection` "
  187. "in order to "
  188. "acquire an :class:`_reflection.Inspector`.",
  189. )
  190. def __init__(self, bind: Union[Engine, Connection]):
  191. """Initialize a new :class:`_reflection.Inspector`.
  192. :param bind: a :class:`~sqlalchemy.engine.Connection`,
  193. which is typically an instance of
  194. :class:`~sqlalchemy.engine.Engine` or
  195. :class:`~sqlalchemy.engine.Connection`.
  196. For a dialect-specific instance of :class:`_reflection.Inspector`, see
  197. :meth:`_reflection.Inspector.from_engine`
  198. """
  199. self._init_legacy(bind)
  200. @classmethod
  201. def _construct(
  202. cls, init: Callable[..., Any], bind: Union[Engine, Connection]
  203. ) -> Inspector:
  204. if hasattr(bind.dialect, "inspector"):
  205. cls = bind.dialect.inspector
  206. self = cls.__new__(cls)
  207. init(self, bind)
  208. return self
  209. def _init_legacy(self, bind: Union[Engine, Connection]) -> None:
  210. if hasattr(bind, "exec_driver_sql"):
  211. self._init_connection(bind) # type: ignore[arg-type]
  212. else:
  213. self._init_engine(bind)
  214. def _init_engine(self, engine: Engine) -> None:
  215. self.bind = self.engine = engine
  216. engine.connect().close()
  217. self._op_context_requires_connect = True
  218. self.dialect = self.engine.dialect
  219. self.info_cache = {}
  220. def _init_connection(self, connection: Connection) -> None:
  221. self.bind = connection
  222. self.engine = connection.engine
  223. self._op_context_requires_connect = False
  224. self.dialect = self.engine.dialect
  225. self.info_cache = {}
  226. def clear_cache(self) -> None:
  227. """reset the cache for this :class:`.Inspector`.
  228. Inspection methods that have data cached will emit SQL queries
  229. when next called to get new data.
  230. .. versionadded:: 2.0
  231. """
  232. self.info_cache.clear()
  233. @classmethod
  234. @util.deprecated(
  235. "1.4",
  236. "The from_engine() method on :class:`_reflection.Inspector` "
  237. "is deprecated and "
  238. "will be removed in a future release. Please use the "
  239. ":func:`.sqlalchemy.inspect` "
  240. "function on an :class:`_engine.Engine` or "
  241. ":class:`_engine.Connection` "
  242. "in order to "
  243. "acquire an :class:`_reflection.Inspector`.",
  244. )
  245. def from_engine(cls, bind: Engine) -> Inspector:
  246. """Construct a new dialect-specific Inspector object from the given
  247. engine or connection.
  248. :param bind: a :class:`~sqlalchemy.engine.Connection`
  249. or :class:`~sqlalchemy.engine.Engine`.
  250. This method differs from direct a direct constructor call of
  251. :class:`_reflection.Inspector` in that the
  252. :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
  253. provide a dialect-specific :class:`_reflection.Inspector` instance,
  254. which may
  255. provide additional methods.
  256. See the example at :class:`_reflection.Inspector`.
  257. """
  258. return cls._construct(cls._init_legacy, bind)
  259. @inspection._inspects(Engine)
  260. def _engine_insp(bind: Engine) -> Inspector: # type: ignore[misc]
  261. return Inspector._construct(Inspector._init_engine, bind)
  262. @inspection._inspects(Connection)
  263. def _connection_insp(bind: Connection) -> Inspector: # type: ignore[misc]
  264. return Inspector._construct(Inspector._init_connection, bind)
  265. @contextlib.contextmanager
  266. def _operation_context(self) -> Generator[Connection, None, None]:
  267. """Return a context that optimizes for multiple operations on a single
  268. transaction.
  269. This essentially allows connect()/close() to be called if we detected
  270. that we're against an :class:`_engine.Engine` and not a
  271. :class:`_engine.Connection`.
  272. """
  273. conn: Connection
  274. if self._op_context_requires_connect:
  275. conn = self.bind.connect() # type: ignore[union-attr]
  276. else:
  277. conn = self.bind # type: ignore[assignment]
  278. try:
  279. yield conn
  280. finally:
  281. if self._op_context_requires_connect:
  282. conn.close()
  283. @contextlib.contextmanager
  284. def _inspection_context(self) -> Generator[Inspector, None, None]:
  285. """Return an :class:`_reflection.Inspector`
  286. from this one that will run all
  287. operations on a single connection.
  288. """
  289. with self._operation_context() as conn:
  290. sub_insp = self._construct(self.__class__._init_connection, conn)
  291. sub_insp.info_cache = self.info_cache
  292. yield sub_insp
  293. @property
  294. def default_schema_name(self) -> Optional[str]:
  295. """Return the default schema name presented by the dialect
  296. for the current engine's database user.
  297. E.g. this is typically ``public`` for PostgreSQL and ``dbo``
  298. for SQL Server.
  299. """
  300. return self.dialect.default_schema_name
  301. def get_schema_names(self, **kw: Any) -> List[str]:
  302. r"""Return all schema names.
  303. :param \**kw: Additional keyword argument to pass to the dialect
  304. specific implementation. See the documentation of the dialect
  305. in use for more information.
  306. """
  307. with self._operation_context() as conn:
  308. return self.dialect.get_schema_names(
  309. conn, info_cache=self.info_cache, **kw
  310. )
  311. def get_table_names(
  312. self, schema: Optional[str] = None, **kw: Any
  313. ) -> List[str]:
  314. r"""Return all table names within a particular schema.
  315. The names are expected to be real tables only, not views.
  316. Views are instead returned using the
  317. :meth:`_reflection.Inspector.get_view_names` and/or
  318. :meth:`_reflection.Inspector.get_materialized_view_names`
  319. methods.
  320. :param schema: Schema name. If ``schema`` is left at ``None``, the
  321. database's default schema is
  322. used, else the named schema is searched. If the database does not
  323. support named schemas, behavior is undefined if ``schema`` is not
  324. passed as ``None``. For special quoting, use :class:`.quoted_name`.
  325. :param \**kw: Additional keyword argument to pass to the dialect
  326. specific implementation. See the documentation of the dialect
  327. in use for more information.
  328. .. seealso::
  329. :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`
  330. :attr:`_schema.MetaData.sorted_tables`
  331. """
  332. with self._operation_context() as conn:
  333. return self.dialect.get_table_names(
  334. conn, schema, info_cache=self.info_cache, **kw
  335. )
  336. def has_table(
  337. self, table_name: str, schema: Optional[str] = None, **kw: Any
  338. ) -> bool:
  339. r"""Return True if the backend has a table, view, or temporary
  340. table of the given name.
  341. :param table_name: name of the table to check
  342. :param schema: schema name to query, if not the default schema.
  343. :param \**kw: Additional keyword argument to pass to the dialect
  344. specific implementation. See the documentation of the dialect
  345. in use for more information.
  346. .. versionadded:: 1.4 - the :meth:`.Inspector.has_table` method
  347. replaces the :meth:`_engine.Engine.has_table` method.
  348. .. versionchanged:: 2.0:: :meth:`.Inspector.has_table` now formally
  349. supports checking for additional table-like objects:
  350. * any type of views (plain or materialized)
  351. * temporary tables of any kind
  352. Previously, these two checks were not formally specified and
  353. different dialects would vary in their behavior. The dialect
  354. testing suite now includes tests for all of these object types
  355. and should be supported by all SQLAlchemy-included dialects.
  356. Support among third party dialects may be lagging, however.
  357. """
  358. with self._operation_context() as conn:
  359. return self.dialect.has_table(
  360. conn, table_name, schema, info_cache=self.info_cache, **kw
  361. )
  362. def has_sequence(
  363. self, sequence_name: str, schema: Optional[str] = None, **kw: Any
  364. ) -> bool:
  365. r"""Return True if the backend has a sequence with the given name.
  366. :param sequence_name: name of the sequence to check
  367. :param schema: schema name to query, if not the default schema.
  368. :param \**kw: Additional keyword argument to pass to the dialect
  369. specific implementation. See the documentation of the dialect
  370. in use for more information.
  371. .. versionadded:: 1.4
  372. """
  373. with self._operation_context() as conn:
  374. return self.dialect.has_sequence(
  375. conn, sequence_name, schema, info_cache=self.info_cache, **kw
  376. )
  377. def has_index(
  378. self,
  379. table_name: str,
  380. index_name: str,
  381. schema: Optional[str] = None,
  382. **kw: Any,
  383. ) -> bool:
  384. r"""Check the existence of a particular index name in the database.
  385. :param table_name: the name of the table the index belongs to
  386. :param index_name: the name of the index to check
  387. :param schema: schema name to query, if not the default schema.
  388. :param \**kw: Additional keyword argument to pass to the dialect
  389. specific implementation. See the documentation of the dialect
  390. in use for more information.
  391. .. versionadded:: 2.0
  392. """
  393. with self._operation_context() as conn:
  394. return self.dialect.has_index(
  395. conn,
  396. table_name,
  397. index_name,
  398. schema,
  399. info_cache=self.info_cache,
  400. **kw,
  401. )
  402. def has_schema(self, schema_name: str, **kw: Any) -> bool:
  403. r"""Return True if the backend has a schema with the given name.
  404. :param schema_name: name of the schema to check
  405. :param \**kw: Additional keyword argument to pass to the dialect
  406. specific implementation. See the documentation of the dialect
  407. in use for more information.
  408. .. versionadded:: 2.0
  409. """
  410. with self._operation_context() as conn:
  411. return self.dialect.has_schema(
  412. conn, schema_name, info_cache=self.info_cache, **kw
  413. )
  414. def get_sorted_table_and_fkc_names(
  415. self,
  416. schema: Optional[str] = None,
  417. **kw: Any,
  418. ) -> List[Tuple[Optional[str], List[Tuple[str, Optional[str]]]]]:
  419. r"""Return dependency-sorted table and foreign key constraint names in
  420. referred to within a particular schema.
  421. This will yield 2-tuples of
  422. ``(tablename, [(tname, fkname), (tname, fkname), ...])``
  423. consisting of table names in CREATE order grouped with the foreign key
  424. constraint names that are not detected as belonging to a cycle.
  425. The final element
  426. will be ``(None, [(tname, fkname), (tname, fkname), ..])``
  427. which will consist of remaining
  428. foreign key constraint names that would require a separate CREATE
  429. step after-the-fact, based on dependencies between tables.
  430. :param schema: schema name to query, if not the default schema.
  431. :param \**kw: Additional keyword argument to pass to the dialect
  432. specific implementation. See the documentation of the dialect
  433. in use for more information.
  434. .. seealso::
  435. :meth:`_reflection.Inspector.get_table_names`
  436. :func:`.sort_tables_and_constraints` - similar method which works
  437. with an already-given :class:`_schema.MetaData`.
  438. """
  439. return [
  440. (
  441. table_key[1] if table_key else None,
  442. [(tname, fks) for (_, tname), fks in fk_collection],
  443. )
  444. for (
  445. table_key,
  446. fk_collection,
  447. ) in self.sort_tables_on_foreign_key_dependency(
  448. consider_schemas=(schema,)
  449. )
  450. ]
  451. def sort_tables_on_foreign_key_dependency(
  452. self,
  453. consider_schemas: Collection[Optional[str]] = (None,),
  454. **kw: Any,
  455. ) -> List[
  456. Tuple[
  457. Optional[Tuple[Optional[str], str]],
  458. List[Tuple[Tuple[Optional[str], str], Optional[str]]],
  459. ]
  460. ]:
  461. r"""Return dependency-sorted table and foreign key constraint names
  462. referred to within multiple schemas.
  463. This method may be compared to
  464. :meth:`.Inspector.get_sorted_table_and_fkc_names`, which
  465. works on one schema at a time; here, the method is a generalization
  466. that will consider multiple schemas at once including that it will
  467. resolve for cross-schema foreign keys.
  468. .. versionadded:: 2.0
  469. """
  470. SchemaTab = Tuple[Optional[str], str]
  471. tuples: Set[Tuple[SchemaTab, SchemaTab]] = set()
  472. remaining_fkcs: Set[Tuple[SchemaTab, Optional[str]]] = set()
  473. fknames_for_table: Dict[SchemaTab, Set[Optional[str]]] = {}
  474. tnames: List[SchemaTab] = []
  475. for schname in consider_schemas:
  476. schema_fkeys = self.get_multi_foreign_keys(schname, **kw)
  477. tnames.extend(schema_fkeys)
  478. for (_, tname), fkeys in schema_fkeys.items():
  479. fknames_for_table[(schname, tname)] = {
  480. fk["name"] for fk in fkeys
  481. }
  482. for fkey in fkeys:
  483. if (
  484. tname != fkey["referred_table"]
  485. or schname != fkey["referred_schema"]
  486. ):
  487. tuples.add(
  488. (
  489. (
  490. fkey["referred_schema"],
  491. fkey["referred_table"],
  492. ),
  493. (schname, tname),
  494. )
  495. )
  496. try:
  497. candidate_sort = list(topological.sort(tuples, tnames))
  498. except exc.CircularDependencyError as err:
  499. edge: Tuple[SchemaTab, SchemaTab]
  500. for edge in err.edges:
  501. tuples.remove(edge)
  502. remaining_fkcs.update(
  503. (edge[1], fkc) for fkc in fknames_for_table[edge[1]]
  504. )
  505. candidate_sort = list(topological.sort(tuples, tnames))
  506. ret: List[
  507. Tuple[Optional[SchemaTab], List[Tuple[SchemaTab, Optional[str]]]]
  508. ]
  509. ret = [
  510. (
  511. (schname, tname),
  512. [
  513. ((schname, tname), fk)
  514. for fk in fknames_for_table[(schname, tname)].difference(
  515. name for _, name in remaining_fkcs
  516. )
  517. ],
  518. )
  519. for (schname, tname) in candidate_sort
  520. ]
  521. return ret + [(None, list(remaining_fkcs))]
  522. def get_temp_table_names(self, **kw: Any) -> List[str]:
  523. r"""Return a list of temporary table names for the current bind.
  524. This method is unsupported by most dialects; currently
  525. only Oracle Database, PostgreSQL and SQLite implements it.
  526. :param \**kw: Additional keyword argument to pass to the dialect
  527. specific implementation. See the documentation of the dialect
  528. in use for more information.
  529. """
  530. with self._operation_context() as conn:
  531. return self.dialect.get_temp_table_names(
  532. conn, info_cache=self.info_cache, **kw
  533. )
  534. def get_temp_view_names(self, **kw: Any) -> List[str]:
  535. r"""Return a list of temporary view names for the current bind.
  536. This method is unsupported by most dialects; currently
  537. only PostgreSQL and SQLite implements it.
  538. :param \**kw: Additional keyword argument to pass to the dialect
  539. specific implementation. See the documentation of the dialect
  540. in use for more information.
  541. """
  542. with self._operation_context() as conn:
  543. return self.dialect.get_temp_view_names(
  544. conn, info_cache=self.info_cache, **kw
  545. )
  546. def get_table_options(
  547. self, table_name: str, schema: Optional[str] = None, **kw: Any
  548. ) -> Dict[str, Any]:
  549. r"""Return a dictionary of options specified when the table of the
  550. given name was created.
  551. This currently includes some options that apply to MySQL and Oracle
  552. Database tables.
  553. :param table_name: string name of the table. For special quoting,
  554. use :class:`.quoted_name`.
  555. :param schema: string schema name; if omitted, uses the default schema
  556. of the database connection. For special quoting,
  557. use :class:`.quoted_name`.
  558. :param \**kw: Additional keyword argument to pass to the dialect
  559. specific implementation. See the documentation of the dialect
  560. in use for more information.
  561. :return: a dict with the table options. The returned keys depend on the
  562. dialect in use. Each one is prefixed with the dialect name.
  563. .. seealso:: :meth:`Inspector.get_multi_table_options`
  564. """
  565. with self._operation_context() as conn:
  566. return self.dialect.get_table_options(
  567. conn, table_name, schema, info_cache=self.info_cache, **kw
  568. )
  569. def get_multi_table_options(
  570. self,
  571. schema: Optional[str] = None,
  572. filter_names: Optional[Sequence[str]] = None,
  573. kind: ObjectKind = ObjectKind.TABLE,
  574. scope: ObjectScope = ObjectScope.DEFAULT,
  575. **kw: Any,
  576. ) -> Dict[TableKey, Dict[str, Any]]:
  577. r"""Return a dictionary of options specified when the tables in the
  578. given schema were created.
  579. The tables can be filtered by passing the names to use to
  580. ``filter_names``.
  581. This currently includes some options that apply to MySQL and Oracle
  582. tables.
  583. :param schema: string schema name; if omitted, uses the default schema
  584. of the database connection. For special quoting,
  585. use :class:`.quoted_name`.
  586. :param filter_names: optionally return information only for the
  587. objects listed here.
  588. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  589. to reflect. Defaults to ``ObjectKind.TABLE``.
  590. :param scope: a :class:`.ObjectScope` that specifies if options of
  591. default, temporary or any tables should be reflected.
  592. Defaults to ``ObjectScope.DEFAULT``.
  593. :param \**kw: Additional keyword argument to pass to the dialect
  594. specific implementation. See the documentation of the dialect
  595. in use for more information.
  596. :return: a dictionary where the keys are two-tuple schema,table-name
  597. and the values are dictionaries with the table options.
  598. The returned keys in each dict depend on the
  599. dialect in use. Each one is prefixed with the dialect name.
  600. The schema is ``None`` if no schema is provided.
  601. .. versionadded:: 2.0
  602. .. seealso:: :meth:`Inspector.get_table_options`
  603. """
  604. with self._operation_context() as conn:
  605. res = self.dialect.get_multi_table_options(
  606. conn,
  607. schema=schema,
  608. filter_names=filter_names,
  609. kind=kind,
  610. scope=scope,
  611. info_cache=self.info_cache,
  612. **kw,
  613. )
  614. return dict(res)
  615. def get_view_names(
  616. self, schema: Optional[str] = None, **kw: Any
  617. ) -> List[str]:
  618. r"""Return all non-materialized view names in `schema`.
  619. :param schema: Optional, retrieve names from a non-default schema.
  620. For special quoting, use :class:`.quoted_name`.
  621. :param \**kw: Additional keyword argument to pass to the dialect
  622. specific implementation. See the documentation of the dialect
  623. in use for more information.
  624. .. versionchanged:: 2.0 For those dialects that previously included
  625. the names of materialized views in this list (currently PostgreSQL),
  626. this method no longer returns the names of materialized views.
  627. the :meth:`.Inspector.get_materialized_view_names` method should
  628. be used instead.
  629. .. seealso::
  630. :meth:`.Inspector.get_materialized_view_names`
  631. """
  632. with self._operation_context() as conn:
  633. return self.dialect.get_view_names(
  634. conn, schema, info_cache=self.info_cache, **kw
  635. )
  636. def get_materialized_view_names(
  637. self, schema: Optional[str] = None, **kw: Any
  638. ) -> List[str]:
  639. r"""Return all materialized view names in `schema`.
  640. :param schema: Optional, retrieve names from a non-default schema.
  641. For special quoting, use :class:`.quoted_name`.
  642. :param \**kw: Additional keyword argument to pass to the dialect
  643. specific implementation. See the documentation of the dialect
  644. in use for more information.
  645. .. versionadded:: 2.0
  646. .. seealso::
  647. :meth:`.Inspector.get_view_names`
  648. """
  649. with self._operation_context() as conn:
  650. return self.dialect.get_materialized_view_names(
  651. conn, schema, info_cache=self.info_cache, **kw
  652. )
  653. def get_sequence_names(
  654. self, schema: Optional[str] = None, **kw: Any
  655. ) -> List[str]:
  656. r"""Return all sequence names in `schema`.
  657. :param schema: Optional, retrieve names from a non-default schema.
  658. For special quoting, use :class:`.quoted_name`.
  659. :param \**kw: Additional keyword argument to pass to the dialect
  660. specific implementation. See the documentation of the dialect
  661. in use for more information.
  662. """
  663. with self._operation_context() as conn:
  664. return self.dialect.get_sequence_names(
  665. conn, schema, info_cache=self.info_cache, **kw
  666. )
  667. def get_view_definition(
  668. self, view_name: str, schema: Optional[str] = None, **kw: Any
  669. ) -> str:
  670. r"""Return definition for the plain or materialized view called
  671. ``view_name``.
  672. :param view_name: Name of the view.
  673. :param schema: Optional, retrieve names from a non-default schema.
  674. For special quoting, use :class:`.quoted_name`.
  675. :param \**kw: Additional keyword argument to pass to the dialect
  676. specific implementation. See the documentation of the dialect
  677. in use for more information.
  678. """
  679. with self._operation_context() as conn:
  680. return self.dialect.get_view_definition(
  681. conn, view_name, schema, info_cache=self.info_cache, **kw
  682. )
  683. def get_columns(
  684. self, table_name: str, schema: Optional[str] = None, **kw: Any
  685. ) -> List[ReflectedColumn]:
  686. r"""Return information about columns in ``table_name``.
  687. Given a string ``table_name`` and an optional string ``schema``,
  688. return column information as a list of :class:`.ReflectedColumn`.
  689. :param table_name: string name of the table. For special quoting,
  690. use :class:`.quoted_name`.
  691. :param schema: string schema name; if omitted, uses the default schema
  692. of the database connection. For special quoting,
  693. use :class:`.quoted_name`.
  694. :param \**kw: Additional keyword argument to pass to the dialect
  695. specific implementation. See the documentation of the dialect
  696. in use for more information.
  697. :return: list of dictionaries, each representing the definition of
  698. a database column.
  699. .. seealso:: :meth:`Inspector.get_multi_columns`.
  700. """
  701. with self._operation_context() as conn:
  702. col_defs = self.dialect.get_columns(
  703. conn, table_name, schema, info_cache=self.info_cache, **kw
  704. )
  705. if col_defs:
  706. self._instantiate_types([col_defs])
  707. return col_defs
  708. def _instantiate_types(
  709. self, data: Iterable[List[ReflectedColumn]]
  710. ) -> None:
  711. # make this easy and only return instances for coltype
  712. for col_defs in data:
  713. for col_def in col_defs:
  714. coltype = col_def["type"]
  715. if not isinstance(coltype, TypeEngine):
  716. col_def["type"] = coltype()
  717. def get_multi_columns(
  718. self,
  719. schema: Optional[str] = None,
  720. filter_names: Optional[Sequence[str]] = None,
  721. kind: ObjectKind = ObjectKind.TABLE,
  722. scope: ObjectScope = ObjectScope.DEFAULT,
  723. **kw: Any,
  724. ) -> Dict[TableKey, List[ReflectedColumn]]:
  725. r"""Return information about columns in all objects in the given
  726. schema.
  727. The objects can be filtered by passing the names to use to
  728. ``filter_names``.
  729. For each table the value is a list of :class:`.ReflectedColumn`.
  730. :param schema: string schema name; if omitted, uses the default schema
  731. of the database connection. For special quoting,
  732. use :class:`.quoted_name`.
  733. :param filter_names: optionally return information only for the
  734. objects listed here.
  735. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  736. to reflect. Defaults to ``ObjectKind.TABLE``.
  737. :param scope: a :class:`.ObjectScope` that specifies if columns of
  738. default, temporary or any tables should be reflected.
  739. Defaults to ``ObjectScope.DEFAULT``.
  740. :param \**kw: Additional keyword argument to pass to the dialect
  741. specific implementation. See the documentation of the dialect
  742. in use for more information.
  743. :return: a dictionary where the keys are two-tuple schema,table-name
  744. and the values are list of dictionaries, each representing the
  745. definition of a database column.
  746. The schema is ``None`` if no schema is provided.
  747. .. versionadded:: 2.0
  748. .. seealso:: :meth:`Inspector.get_columns`
  749. """
  750. with self._operation_context() as conn:
  751. table_col_defs = dict(
  752. self.dialect.get_multi_columns(
  753. conn,
  754. schema=schema,
  755. filter_names=filter_names,
  756. kind=kind,
  757. scope=scope,
  758. info_cache=self.info_cache,
  759. **kw,
  760. )
  761. )
  762. self._instantiate_types(table_col_defs.values())
  763. return table_col_defs
  764. def get_pk_constraint(
  765. self, table_name: str, schema: Optional[str] = None, **kw: Any
  766. ) -> ReflectedPrimaryKeyConstraint:
  767. r"""Return information about primary key constraint in ``table_name``.
  768. Given a string ``table_name``, and an optional string `schema`, return
  769. primary key information as a :class:`.ReflectedPrimaryKeyConstraint`.
  770. :param table_name: string name of the table. For special quoting,
  771. use :class:`.quoted_name`.
  772. :param schema: string schema name; if omitted, uses the default schema
  773. of the database connection. For special quoting,
  774. use :class:`.quoted_name`.
  775. :param \**kw: Additional keyword argument to pass to the dialect
  776. specific implementation. See the documentation of the dialect
  777. in use for more information.
  778. :return: a dictionary representing the definition of
  779. a primary key constraint.
  780. .. seealso:: :meth:`Inspector.get_multi_pk_constraint`
  781. """
  782. with self._operation_context() as conn:
  783. return self.dialect.get_pk_constraint(
  784. conn, table_name, schema, info_cache=self.info_cache, **kw
  785. )
  786. def get_multi_pk_constraint(
  787. self,
  788. schema: Optional[str] = None,
  789. filter_names: Optional[Sequence[str]] = None,
  790. kind: ObjectKind = ObjectKind.TABLE,
  791. scope: ObjectScope = ObjectScope.DEFAULT,
  792. **kw: Any,
  793. ) -> Dict[TableKey, ReflectedPrimaryKeyConstraint]:
  794. r"""Return information about primary key constraints in
  795. all tables in the given schema.
  796. The tables can be filtered by passing the names to use to
  797. ``filter_names``.
  798. For each table the value is a :class:`.ReflectedPrimaryKeyConstraint`.
  799. :param schema: string schema name; if omitted, uses the default schema
  800. of the database connection. For special quoting,
  801. use :class:`.quoted_name`.
  802. :param filter_names: optionally return information only for the
  803. objects listed here.
  804. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  805. to reflect. Defaults to ``ObjectKind.TABLE``.
  806. :param scope: a :class:`.ObjectScope` that specifies if primary keys of
  807. default, temporary or any tables should be reflected.
  808. Defaults to ``ObjectScope.DEFAULT``.
  809. :param \**kw: Additional keyword argument to pass to the dialect
  810. specific implementation. See the documentation of the dialect
  811. in use for more information.
  812. :return: a dictionary where the keys are two-tuple schema,table-name
  813. and the values are dictionaries, each representing the
  814. definition of a primary key constraint.
  815. The schema is ``None`` if no schema is provided.
  816. .. versionadded:: 2.0
  817. .. seealso:: :meth:`Inspector.get_pk_constraint`
  818. """
  819. with self._operation_context() as conn:
  820. return dict(
  821. self.dialect.get_multi_pk_constraint(
  822. conn,
  823. schema=schema,
  824. filter_names=filter_names,
  825. kind=kind,
  826. scope=scope,
  827. info_cache=self.info_cache,
  828. **kw,
  829. )
  830. )
  831. def get_foreign_keys(
  832. self, table_name: str, schema: Optional[str] = None, **kw: Any
  833. ) -> List[ReflectedForeignKeyConstraint]:
  834. r"""Return information about foreign_keys in ``table_name``.
  835. Given a string ``table_name``, and an optional string `schema`, return
  836. foreign key information as a list of
  837. :class:`.ReflectedForeignKeyConstraint`.
  838. :param table_name: string name of the table. For special quoting,
  839. use :class:`.quoted_name`.
  840. :param schema: string schema name; if omitted, uses the default schema
  841. of the database connection. For special quoting,
  842. use :class:`.quoted_name`.
  843. :param \**kw: Additional keyword argument to pass to the dialect
  844. specific implementation. See the documentation of the dialect
  845. in use for more information.
  846. :return: a list of dictionaries, each representing the
  847. a foreign key definition.
  848. .. seealso:: :meth:`Inspector.get_multi_foreign_keys`
  849. """
  850. with self._operation_context() as conn:
  851. return self.dialect.get_foreign_keys(
  852. conn, table_name, schema, info_cache=self.info_cache, **kw
  853. )
  854. def get_multi_foreign_keys(
  855. self,
  856. schema: Optional[str] = None,
  857. filter_names: Optional[Sequence[str]] = None,
  858. kind: ObjectKind = ObjectKind.TABLE,
  859. scope: ObjectScope = ObjectScope.DEFAULT,
  860. **kw: Any,
  861. ) -> Dict[TableKey, List[ReflectedForeignKeyConstraint]]:
  862. r"""Return information about foreign_keys in all tables
  863. in the given schema.
  864. The tables can be filtered by passing the names to use to
  865. ``filter_names``.
  866. For each table the value is a list of
  867. :class:`.ReflectedForeignKeyConstraint`.
  868. :param schema: string schema name; if omitted, uses the default schema
  869. of the database connection. For special quoting,
  870. use :class:`.quoted_name`.
  871. :param filter_names: optionally return information only for the
  872. objects listed here.
  873. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  874. to reflect. Defaults to ``ObjectKind.TABLE``.
  875. :param scope: a :class:`.ObjectScope` that specifies if foreign keys of
  876. default, temporary or any tables should be reflected.
  877. Defaults to ``ObjectScope.DEFAULT``.
  878. :param \**kw: Additional keyword argument to pass to the dialect
  879. specific implementation. See the documentation of the dialect
  880. in use for more information.
  881. :return: a dictionary where the keys are two-tuple schema,table-name
  882. and the values are list of dictionaries, each representing
  883. a foreign key definition.
  884. The schema is ``None`` if no schema is provided.
  885. .. versionadded:: 2.0
  886. .. seealso:: :meth:`Inspector.get_foreign_keys`
  887. """
  888. with self._operation_context() as conn:
  889. return dict(
  890. self.dialect.get_multi_foreign_keys(
  891. conn,
  892. schema=schema,
  893. filter_names=filter_names,
  894. kind=kind,
  895. scope=scope,
  896. info_cache=self.info_cache,
  897. **kw,
  898. )
  899. )
  900. def get_indexes(
  901. self, table_name: str, schema: Optional[str] = None, **kw: Any
  902. ) -> List[ReflectedIndex]:
  903. r"""Return information about indexes in ``table_name``.
  904. Given a string ``table_name`` and an optional string `schema`, return
  905. index information as a list of :class:`.ReflectedIndex`.
  906. :param table_name: string name of the table. For special quoting,
  907. use :class:`.quoted_name`.
  908. :param schema: string schema name; if omitted, uses the default schema
  909. of the database connection. For special quoting,
  910. use :class:`.quoted_name`.
  911. :param \**kw: Additional keyword argument to pass to the dialect
  912. specific implementation. See the documentation of the dialect
  913. in use for more information.
  914. :return: a list of dictionaries, each representing the
  915. definition of an index.
  916. .. seealso:: :meth:`Inspector.get_multi_indexes`
  917. """
  918. with self._operation_context() as conn:
  919. return self.dialect.get_indexes(
  920. conn, table_name, schema, info_cache=self.info_cache, **kw
  921. )
  922. def get_multi_indexes(
  923. self,
  924. schema: Optional[str] = None,
  925. filter_names: Optional[Sequence[str]] = None,
  926. kind: ObjectKind = ObjectKind.TABLE,
  927. scope: ObjectScope = ObjectScope.DEFAULT,
  928. **kw: Any,
  929. ) -> Dict[TableKey, List[ReflectedIndex]]:
  930. r"""Return information about indexes in in all objects
  931. in the given schema.
  932. The objects can be filtered by passing the names to use to
  933. ``filter_names``.
  934. For each table the value is a list of :class:`.ReflectedIndex`.
  935. :param schema: string schema name; if omitted, uses the default schema
  936. of the database connection. For special quoting,
  937. use :class:`.quoted_name`.
  938. :param filter_names: optionally return information only for the
  939. objects listed here.
  940. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  941. to reflect. Defaults to ``ObjectKind.TABLE``.
  942. :param scope: a :class:`.ObjectScope` that specifies if indexes of
  943. default, temporary or any tables should be reflected.
  944. Defaults to ``ObjectScope.DEFAULT``.
  945. :param \**kw: Additional keyword argument to pass to the dialect
  946. specific implementation. See the documentation of the dialect
  947. in use for more information.
  948. :return: a dictionary where the keys are two-tuple schema,table-name
  949. and the values are list of dictionaries, each representing the
  950. definition of an index.
  951. The schema is ``None`` if no schema is provided.
  952. .. versionadded:: 2.0
  953. .. seealso:: :meth:`Inspector.get_indexes`
  954. """
  955. with self._operation_context() as conn:
  956. return dict(
  957. self.dialect.get_multi_indexes(
  958. conn,
  959. schema=schema,
  960. filter_names=filter_names,
  961. kind=kind,
  962. scope=scope,
  963. info_cache=self.info_cache,
  964. **kw,
  965. )
  966. )
  967. def get_unique_constraints(
  968. self, table_name: str, schema: Optional[str] = None, **kw: Any
  969. ) -> List[ReflectedUniqueConstraint]:
  970. r"""Return information about unique constraints in ``table_name``.
  971. Given a string ``table_name`` and an optional string `schema`, return
  972. unique constraint information as a list of
  973. :class:`.ReflectedUniqueConstraint`.
  974. :param table_name: string name of the table. For special quoting,
  975. use :class:`.quoted_name`.
  976. :param schema: string schema name; if omitted, uses the default schema
  977. of the database connection. For special quoting,
  978. use :class:`.quoted_name`.
  979. :param \**kw: Additional keyword argument to pass to the dialect
  980. specific implementation. See the documentation of the dialect
  981. in use for more information.
  982. :return: a list of dictionaries, each representing the
  983. definition of an unique constraint.
  984. .. seealso:: :meth:`Inspector.get_multi_unique_constraints`
  985. """
  986. with self._operation_context() as conn:
  987. return self.dialect.get_unique_constraints(
  988. conn, table_name, schema, info_cache=self.info_cache, **kw
  989. )
  990. def get_multi_unique_constraints(
  991. self,
  992. schema: Optional[str] = None,
  993. filter_names: Optional[Sequence[str]] = None,
  994. kind: ObjectKind = ObjectKind.TABLE,
  995. scope: ObjectScope = ObjectScope.DEFAULT,
  996. **kw: Any,
  997. ) -> Dict[TableKey, List[ReflectedUniqueConstraint]]:
  998. r"""Return information about unique constraints in all tables
  999. in the given schema.
  1000. The tables can be filtered by passing the names to use to
  1001. ``filter_names``.
  1002. For each table the value is a list of
  1003. :class:`.ReflectedUniqueConstraint`.
  1004. :param schema: string schema name; if omitted, uses the default schema
  1005. of the database connection. For special quoting,
  1006. use :class:`.quoted_name`.
  1007. :param filter_names: optionally return information only for the
  1008. objects listed here.
  1009. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  1010. to reflect. Defaults to ``ObjectKind.TABLE``.
  1011. :param scope: a :class:`.ObjectScope` that specifies if constraints of
  1012. default, temporary or any tables should be reflected.
  1013. Defaults to ``ObjectScope.DEFAULT``.
  1014. :param \**kw: Additional keyword argument to pass to the dialect
  1015. specific implementation. See the documentation of the dialect
  1016. in use for more information.
  1017. :return: a dictionary where the keys are two-tuple schema,table-name
  1018. and the values are list of dictionaries, each representing the
  1019. definition of an unique constraint.
  1020. The schema is ``None`` if no schema is provided.
  1021. .. versionadded:: 2.0
  1022. .. seealso:: :meth:`Inspector.get_unique_constraints`
  1023. """
  1024. with self._operation_context() as conn:
  1025. return dict(
  1026. self.dialect.get_multi_unique_constraints(
  1027. conn,
  1028. schema=schema,
  1029. filter_names=filter_names,
  1030. kind=kind,
  1031. scope=scope,
  1032. info_cache=self.info_cache,
  1033. **kw,
  1034. )
  1035. )
  1036. def get_table_comment(
  1037. self, table_name: str, schema: Optional[str] = None, **kw: Any
  1038. ) -> ReflectedTableComment:
  1039. r"""Return information about the table comment for ``table_name``.
  1040. Given a string ``table_name`` and an optional string ``schema``,
  1041. return table comment information as a :class:`.ReflectedTableComment`.
  1042. Raises ``NotImplementedError`` for a dialect that does not support
  1043. comments.
  1044. :param table_name: string name of the table. For special quoting,
  1045. use :class:`.quoted_name`.
  1046. :param schema: string schema name; if omitted, uses the default schema
  1047. of the database connection. For special quoting,
  1048. use :class:`.quoted_name`.
  1049. :param \**kw: Additional keyword argument to pass to the dialect
  1050. specific implementation. See the documentation of the dialect
  1051. in use for more information.
  1052. :return: a dictionary, with the table comment.
  1053. .. versionadded:: 1.2
  1054. .. seealso:: :meth:`Inspector.get_multi_table_comment`
  1055. """
  1056. with self._operation_context() as conn:
  1057. return self.dialect.get_table_comment(
  1058. conn, table_name, schema, info_cache=self.info_cache, **kw
  1059. )
  1060. def get_multi_table_comment(
  1061. self,
  1062. schema: Optional[str] = None,
  1063. filter_names: Optional[Sequence[str]] = None,
  1064. kind: ObjectKind = ObjectKind.TABLE,
  1065. scope: ObjectScope = ObjectScope.DEFAULT,
  1066. **kw: Any,
  1067. ) -> Dict[TableKey, ReflectedTableComment]:
  1068. r"""Return information about the table comment in all objects
  1069. in the given schema.
  1070. The objects can be filtered by passing the names to use to
  1071. ``filter_names``.
  1072. For each table the value is a :class:`.ReflectedTableComment`.
  1073. Raises ``NotImplementedError`` for a dialect that does not support
  1074. comments.
  1075. :param schema: string schema name; if omitted, uses the default schema
  1076. of the database connection. For special quoting,
  1077. use :class:`.quoted_name`.
  1078. :param filter_names: optionally return information only for the
  1079. objects listed here.
  1080. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  1081. to reflect. Defaults to ``ObjectKind.TABLE``.
  1082. :param scope: a :class:`.ObjectScope` that specifies if comments of
  1083. default, temporary or any tables should be reflected.
  1084. Defaults to ``ObjectScope.DEFAULT``.
  1085. :param \**kw: Additional keyword argument to pass to the dialect
  1086. specific implementation. See the documentation of the dialect
  1087. in use for more information.
  1088. :return: a dictionary where the keys are two-tuple schema,table-name
  1089. and the values are dictionaries, representing the
  1090. table comments.
  1091. The schema is ``None`` if no schema is provided.
  1092. .. versionadded:: 2.0
  1093. .. seealso:: :meth:`Inspector.get_table_comment`
  1094. """
  1095. with self._operation_context() as conn:
  1096. return dict(
  1097. self.dialect.get_multi_table_comment(
  1098. conn,
  1099. schema=schema,
  1100. filter_names=filter_names,
  1101. kind=kind,
  1102. scope=scope,
  1103. info_cache=self.info_cache,
  1104. **kw,
  1105. )
  1106. )
  1107. def get_check_constraints(
  1108. self, table_name: str, schema: Optional[str] = None, **kw: Any
  1109. ) -> List[ReflectedCheckConstraint]:
  1110. r"""Return information about check constraints in ``table_name``.
  1111. Given a string ``table_name`` and an optional string `schema`, return
  1112. check constraint information as a list of
  1113. :class:`.ReflectedCheckConstraint`.
  1114. :param table_name: string name of the table. For special quoting,
  1115. use :class:`.quoted_name`.
  1116. :param schema: string schema name; if omitted, uses the default schema
  1117. of the database connection. For special quoting,
  1118. use :class:`.quoted_name`.
  1119. :param \**kw: Additional keyword argument to pass to the dialect
  1120. specific implementation. See the documentation of the dialect
  1121. in use for more information.
  1122. :return: a list of dictionaries, each representing the
  1123. definition of a check constraints.
  1124. .. seealso:: :meth:`Inspector.get_multi_check_constraints`
  1125. """
  1126. with self._operation_context() as conn:
  1127. return self.dialect.get_check_constraints(
  1128. conn, table_name, schema, info_cache=self.info_cache, **kw
  1129. )
  1130. def get_multi_check_constraints(
  1131. self,
  1132. schema: Optional[str] = None,
  1133. filter_names: Optional[Sequence[str]] = None,
  1134. kind: ObjectKind = ObjectKind.TABLE,
  1135. scope: ObjectScope = ObjectScope.DEFAULT,
  1136. **kw: Any,
  1137. ) -> Dict[TableKey, List[ReflectedCheckConstraint]]:
  1138. r"""Return information about check constraints in all tables
  1139. in the given schema.
  1140. The tables can be filtered by passing the names to use to
  1141. ``filter_names``.
  1142. For each table the value is a list of
  1143. :class:`.ReflectedCheckConstraint`.
  1144. :param schema: string schema name; if omitted, uses the default schema
  1145. of the database connection. For special quoting,
  1146. use :class:`.quoted_name`.
  1147. :param filter_names: optionally return information only for the
  1148. objects listed here.
  1149. :param kind: a :class:`.ObjectKind` that specifies the type of objects
  1150. to reflect. Defaults to ``ObjectKind.TABLE``.
  1151. :param scope: a :class:`.ObjectScope` that specifies if constraints of
  1152. default, temporary or any tables should be reflected.
  1153. Defaults to ``ObjectScope.DEFAULT``.
  1154. :param \**kw: Additional keyword argument to pass to the dialect
  1155. specific implementation. See the documentation of the dialect
  1156. in use for more information.
  1157. :return: a dictionary where the keys are two-tuple schema,table-name
  1158. and the values are list of dictionaries, each representing the
  1159. definition of a check constraints.
  1160. The schema is ``None`` if no schema is provided.
  1161. .. versionadded:: 2.0
  1162. .. seealso:: :meth:`Inspector.get_check_constraints`
  1163. """
  1164. with self._operation_context() as conn:
  1165. return dict(
  1166. self.dialect.get_multi_check_constraints(
  1167. conn,
  1168. schema=schema,
  1169. filter_names=filter_names,
  1170. kind=kind,
  1171. scope=scope,
  1172. info_cache=self.info_cache,
  1173. **kw,
  1174. )
  1175. )
  1176. def reflect_table(
  1177. self,
  1178. table: sa_schema.Table,
  1179. include_columns: Optional[Collection[str]],
  1180. exclude_columns: Collection[str] = (),
  1181. resolve_fks: bool = True,
  1182. _extend_on: Optional[Set[sa_schema.Table]] = None,
  1183. _reflect_info: Optional[_ReflectionInfo] = None,
  1184. ) -> None:
  1185. """Given a :class:`_schema.Table` object, load its internal
  1186. constructs based on introspection.
  1187. This is the underlying method used by most dialects to produce
  1188. table reflection. Direct usage is like::
  1189. from sqlalchemy import create_engine, MetaData, Table
  1190. from sqlalchemy import inspect
  1191. engine = create_engine("...")
  1192. meta = MetaData()
  1193. user_table = Table("user", meta)
  1194. insp = inspect(engine)
  1195. insp.reflect_table(user_table, None)
  1196. .. versionchanged:: 1.4 Renamed from ``reflecttable`` to
  1197. ``reflect_table``
  1198. :param table: a :class:`~sqlalchemy.schema.Table` instance.
  1199. :param include_columns: a list of string column names to include
  1200. in the reflection process. If ``None``, all columns are reflected.
  1201. """
  1202. if _extend_on is not None:
  1203. if table in _extend_on:
  1204. return
  1205. else:
  1206. _extend_on.add(table)
  1207. dialect = self.bind.dialect
  1208. with self._operation_context() as conn:
  1209. schema = conn.schema_for_object(table)
  1210. table_name = table.name
  1211. # get table-level arguments that are specifically
  1212. # intended for reflection, e.g. oracle_resolve_synonyms.
  1213. # these are unconditionally passed to related Table
  1214. # objects
  1215. reflection_options = {
  1216. k: table.dialect_kwargs.get(k)
  1217. for k in dialect.reflection_options
  1218. if k in table.dialect_kwargs
  1219. }
  1220. table_key = (schema, table_name)
  1221. if _reflect_info is None or table_key not in _reflect_info.columns:
  1222. _reflect_info = self._get_reflection_info(
  1223. schema,
  1224. filter_names=[table_name],
  1225. kind=ObjectKind.ANY,
  1226. scope=ObjectScope.ANY,
  1227. _reflect_info=_reflect_info,
  1228. **table.dialect_kwargs,
  1229. )
  1230. if table_key in _reflect_info.unreflectable:
  1231. raise _reflect_info.unreflectable[table_key]
  1232. if table_key not in _reflect_info.columns:
  1233. raise exc.NoSuchTableError(table_name)
  1234. # reflect table options, like mysql_engine
  1235. if _reflect_info.table_options:
  1236. tbl_opts = _reflect_info.table_options.get(table_key)
  1237. if tbl_opts:
  1238. # add additional kwargs to the Table if the dialect
  1239. # returned them
  1240. table._validate_dialect_kwargs(tbl_opts)
  1241. found_table = False
  1242. cols_by_orig_name: Dict[str, sa_schema.Column[Any]] = {}
  1243. for col_d in _reflect_info.columns[table_key]:
  1244. found_table = True
  1245. self._reflect_column(
  1246. table,
  1247. col_d,
  1248. include_columns,
  1249. exclude_columns,
  1250. cols_by_orig_name,
  1251. )
  1252. # NOTE: support tables/views with no columns
  1253. if not found_table and not self.has_table(table_name, schema):
  1254. raise exc.NoSuchTableError(table_name)
  1255. self._reflect_pk(
  1256. _reflect_info, table_key, table, cols_by_orig_name, exclude_columns
  1257. )
  1258. self._reflect_fk(
  1259. _reflect_info,
  1260. table_key,
  1261. table,
  1262. cols_by_orig_name,
  1263. include_columns,
  1264. exclude_columns,
  1265. resolve_fks,
  1266. _extend_on,
  1267. reflection_options,
  1268. )
  1269. self._reflect_indexes(
  1270. _reflect_info,
  1271. table_key,
  1272. table,
  1273. cols_by_orig_name,
  1274. include_columns,
  1275. exclude_columns,
  1276. reflection_options,
  1277. )
  1278. self._reflect_unique_constraints(
  1279. _reflect_info,
  1280. table_key,
  1281. table,
  1282. cols_by_orig_name,
  1283. include_columns,
  1284. exclude_columns,
  1285. reflection_options,
  1286. )
  1287. self._reflect_check_constraints(
  1288. _reflect_info,
  1289. table_key,
  1290. table,
  1291. cols_by_orig_name,
  1292. include_columns,
  1293. exclude_columns,
  1294. reflection_options,
  1295. )
  1296. self._reflect_table_comment(
  1297. _reflect_info,
  1298. table_key,
  1299. table,
  1300. reflection_options,
  1301. )
  1302. def _reflect_column(
  1303. self,
  1304. table: sa_schema.Table,
  1305. col_d: ReflectedColumn,
  1306. include_columns: Optional[Collection[str]],
  1307. exclude_columns: Collection[str],
  1308. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1309. ) -> None:
  1310. orig_name = col_d["name"]
  1311. table.metadata.dispatch.column_reflect(self, table, col_d)
  1312. table.dispatch.column_reflect(self, table, col_d)
  1313. # fetch name again as column_reflect is allowed to
  1314. # change it
  1315. name = col_d["name"]
  1316. if (include_columns and name not in include_columns) or (
  1317. exclude_columns and name in exclude_columns
  1318. ):
  1319. return
  1320. coltype = col_d["type"]
  1321. col_kw = {
  1322. k: col_d[k] # type: ignore[literal-required]
  1323. for k in [
  1324. "nullable",
  1325. "autoincrement",
  1326. "quote",
  1327. "info",
  1328. "key",
  1329. "comment",
  1330. ]
  1331. if k in col_d
  1332. }
  1333. if "dialect_options" in col_d:
  1334. col_kw.update(col_d["dialect_options"])
  1335. colargs = []
  1336. default: Any
  1337. if col_d.get("default") is not None:
  1338. default_text = col_d["default"]
  1339. assert default_text is not None
  1340. if isinstance(default_text, TextClause):
  1341. default = sa_schema.DefaultClause(
  1342. default_text, _reflected=True
  1343. )
  1344. elif not isinstance(default_text, sa_schema.FetchedValue):
  1345. default = sa_schema.DefaultClause(
  1346. sql.text(default_text), _reflected=True
  1347. )
  1348. else:
  1349. default = default_text
  1350. colargs.append(default)
  1351. if "computed" in col_d:
  1352. computed = sa_schema.Computed(**col_d["computed"])
  1353. colargs.append(computed)
  1354. if "identity" in col_d:
  1355. identity = sa_schema.Identity(**col_d["identity"])
  1356. colargs.append(identity)
  1357. cols_by_orig_name[orig_name] = col = sa_schema.Column(
  1358. name, coltype, *colargs, **col_kw
  1359. )
  1360. if col.key in table.primary_key:
  1361. col.primary_key = True
  1362. table.append_column(col, replace_existing=True)
  1363. def _reflect_pk(
  1364. self,
  1365. _reflect_info: _ReflectionInfo,
  1366. table_key: TableKey,
  1367. table: sa_schema.Table,
  1368. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1369. exclude_columns: Collection[str],
  1370. ) -> None:
  1371. pk_cons = _reflect_info.pk_constraint.get(table_key)
  1372. if pk_cons:
  1373. pk_cols = [
  1374. cols_by_orig_name[pk]
  1375. for pk in pk_cons["constrained_columns"]
  1376. if pk in cols_by_orig_name and pk not in exclude_columns
  1377. ]
  1378. # update pk constraint name, comment and dialect_kwargs
  1379. table.primary_key.name = pk_cons.get("name")
  1380. table.primary_key.comment = pk_cons.get("comment", None)
  1381. dialect_options = pk_cons.get("dialect_options")
  1382. if dialect_options:
  1383. table.primary_key.dialect_kwargs.update(dialect_options)
  1384. # tell the PKConstraint to re-initialize
  1385. # its column collection
  1386. table.primary_key._reload(pk_cols)
  1387. def _reflect_fk(
  1388. self,
  1389. _reflect_info: _ReflectionInfo,
  1390. table_key: TableKey,
  1391. table: sa_schema.Table,
  1392. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1393. include_columns: Optional[Collection[str]],
  1394. exclude_columns: Collection[str],
  1395. resolve_fks: bool,
  1396. _extend_on: Optional[Set[sa_schema.Table]],
  1397. reflection_options: Dict[str, Any],
  1398. ) -> None:
  1399. fkeys = _reflect_info.foreign_keys.get(table_key, [])
  1400. for fkey_d in fkeys:
  1401. conname = fkey_d["name"]
  1402. # look for columns by orig name in cols_by_orig_name,
  1403. # but support columns that are in-Python only as fallback
  1404. constrained_columns = [
  1405. cols_by_orig_name[c].key if c in cols_by_orig_name else c
  1406. for c in fkey_d["constrained_columns"]
  1407. ]
  1408. if (
  1409. exclude_columns
  1410. and set(constrained_columns).intersection(exclude_columns)
  1411. or (
  1412. include_columns
  1413. and set(constrained_columns).difference(include_columns)
  1414. )
  1415. ):
  1416. continue
  1417. referred_schema = fkey_d["referred_schema"]
  1418. referred_table = fkey_d["referred_table"]
  1419. referred_columns = fkey_d["referred_columns"]
  1420. refspec = []
  1421. if referred_schema is not None:
  1422. if resolve_fks:
  1423. sa_schema.Table(
  1424. referred_table,
  1425. table.metadata,
  1426. schema=referred_schema,
  1427. autoload_with=self.bind,
  1428. _extend_on=_extend_on,
  1429. _reflect_info=_reflect_info,
  1430. **reflection_options,
  1431. )
  1432. for column in referred_columns:
  1433. refspec.append(
  1434. ".".join([referred_schema, referred_table, column])
  1435. )
  1436. else:
  1437. if resolve_fks:
  1438. sa_schema.Table(
  1439. referred_table,
  1440. table.metadata,
  1441. autoload_with=self.bind,
  1442. schema=sa_schema.BLANK_SCHEMA,
  1443. _extend_on=_extend_on,
  1444. _reflect_info=_reflect_info,
  1445. **reflection_options,
  1446. )
  1447. for column in referred_columns:
  1448. refspec.append(".".join([referred_table, column]))
  1449. if "options" in fkey_d:
  1450. options = fkey_d["options"]
  1451. else:
  1452. options = {}
  1453. try:
  1454. table.append_constraint(
  1455. sa_schema.ForeignKeyConstraint(
  1456. constrained_columns,
  1457. refspec,
  1458. conname,
  1459. link_to_name=True,
  1460. comment=fkey_d.get("comment"),
  1461. **options,
  1462. )
  1463. )
  1464. except exc.ConstraintColumnNotFoundError:
  1465. util.warn(
  1466. f"On reflected table {table.name}, skipping reflection of "
  1467. "foreign key constraint "
  1468. f"{conname}; one or more subject columns within "
  1469. f"name(s) {', '.join(constrained_columns)} are not "
  1470. "present in the table"
  1471. )
  1472. _index_sort_exprs = {
  1473. "asc": operators.asc_op,
  1474. "desc": operators.desc_op,
  1475. "nulls_first": operators.nulls_first_op,
  1476. "nulls_last": operators.nulls_last_op,
  1477. }
  1478. def _reflect_indexes(
  1479. self,
  1480. _reflect_info: _ReflectionInfo,
  1481. table_key: TableKey,
  1482. table: sa_schema.Table,
  1483. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1484. include_columns: Optional[Collection[str]],
  1485. exclude_columns: Collection[str],
  1486. reflection_options: Dict[str, Any],
  1487. ) -> None:
  1488. # Indexes
  1489. indexes = _reflect_info.indexes.get(table_key, [])
  1490. for index_d in indexes:
  1491. name = index_d["name"]
  1492. columns = index_d["column_names"]
  1493. expressions = index_d.get("expressions")
  1494. column_sorting = index_d.get("column_sorting", {})
  1495. unique = index_d["unique"]
  1496. flavor = index_d.get("type", "index")
  1497. dialect_options = index_d.get("dialect_options", {})
  1498. duplicates = index_d.get("duplicates_constraint")
  1499. if include_columns and not set(columns).issubset(include_columns):
  1500. continue
  1501. if duplicates:
  1502. continue
  1503. # look for columns by orig name in cols_by_orig_name,
  1504. # but support columns that are in-Python only as fallback
  1505. idx_element: Any
  1506. idx_elements = []
  1507. for index, c in enumerate(columns):
  1508. if c is None:
  1509. if not expressions:
  1510. util.warn(
  1511. f"Skipping {flavor} {name!r} because key "
  1512. f"{index + 1} reflected as None but no "
  1513. "'expressions' were returned"
  1514. )
  1515. break
  1516. idx_element = sql.text(expressions[index])
  1517. else:
  1518. try:
  1519. if c in cols_by_orig_name:
  1520. idx_element = cols_by_orig_name[c]
  1521. else:
  1522. idx_element = table.c[c]
  1523. except KeyError:
  1524. util.warn(
  1525. f"{flavor} key {c!r} was not located in "
  1526. f"columns for table {table.name!r}"
  1527. )
  1528. continue
  1529. for option in column_sorting.get(c, ()):
  1530. if option in self._index_sort_exprs:
  1531. op = self._index_sort_exprs[option]
  1532. idx_element = op(idx_element)
  1533. idx_elements.append(idx_element)
  1534. else:
  1535. sa_schema.Index(
  1536. name,
  1537. *idx_elements,
  1538. _table=table,
  1539. unique=unique,
  1540. **dialect_options,
  1541. )
  1542. def _reflect_unique_constraints(
  1543. self,
  1544. _reflect_info: _ReflectionInfo,
  1545. table_key: TableKey,
  1546. table: sa_schema.Table,
  1547. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1548. include_columns: Optional[Collection[str]],
  1549. exclude_columns: Collection[str],
  1550. reflection_options: Dict[str, Any],
  1551. ) -> None:
  1552. constraints = _reflect_info.unique_constraints.get(table_key, [])
  1553. # Unique Constraints
  1554. for const_d in constraints:
  1555. conname = const_d["name"]
  1556. columns = const_d["column_names"]
  1557. comment = const_d.get("comment")
  1558. duplicates = const_d.get("duplicates_index")
  1559. dialect_options = const_d.get("dialect_options", {})
  1560. if include_columns and not set(columns).issubset(include_columns):
  1561. continue
  1562. if duplicates:
  1563. continue
  1564. # look for columns by orig name in cols_by_orig_name,
  1565. # but support columns that are in-Python only as fallback
  1566. constrained_cols = []
  1567. for c in columns:
  1568. try:
  1569. constrained_col = (
  1570. cols_by_orig_name[c]
  1571. if c in cols_by_orig_name
  1572. else table.c[c]
  1573. )
  1574. except KeyError:
  1575. util.warn(
  1576. "unique constraint key '%s' was not located in "
  1577. "columns for table '%s'" % (c, table.name)
  1578. )
  1579. else:
  1580. constrained_cols.append(constrained_col)
  1581. table.append_constraint(
  1582. sa_schema.UniqueConstraint(
  1583. *constrained_cols,
  1584. name=conname,
  1585. comment=comment,
  1586. **dialect_options,
  1587. )
  1588. )
  1589. def _reflect_check_constraints(
  1590. self,
  1591. _reflect_info: _ReflectionInfo,
  1592. table_key: TableKey,
  1593. table: sa_schema.Table,
  1594. cols_by_orig_name: Dict[str, sa_schema.Column[Any]],
  1595. include_columns: Optional[Collection[str]],
  1596. exclude_columns: Collection[str],
  1597. reflection_options: Dict[str, Any],
  1598. ) -> None:
  1599. constraints = _reflect_info.check_constraints.get(table_key, [])
  1600. for const_d in constraints:
  1601. table.append_constraint(sa_schema.CheckConstraint(**const_d))
  1602. def _reflect_table_comment(
  1603. self,
  1604. _reflect_info: _ReflectionInfo,
  1605. table_key: TableKey,
  1606. table: sa_schema.Table,
  1607. reflection_options: Dict[str, Any],
  1608. ) -> None:
  1609. comment_dict = _reflect_info.table_comment.get(table_key)
  1610. if comment_dict:
  1611. table.comment = comment_dict["text"]
  1612. def _get_reflection_info(
  1613. self,
  1614. schema: Optional[str] = None,
  1615. filter_names: Optional[Collection[str]] = None,
  1616. available: Optional[Collection[str]] = None,
  1617. _reflect_info: Optional[_ReflectionInfo] = None,
  1618. **kw: Any,
  1619. ) -> _ReflectionInfo:
  1620. kw["schema"] = schema
  1621. if filter_names and available and len(filter_names) > 100:
  1622. fraction = len(filter_names) / len(available)
  1623. else:
  1624. fraction = None
  1625. unreflectable: Dict[TableKey, exc.UnreflectableTableError]
  1626. kw["unreflectable"] = unreflectable = {}
  1627. has_result: bool = True
  1628. def run(
  1629. meth: Any,
  1630. *,
  1631. optional: bool = False,
  1632. check_filter_names_from_meth: bool = False,
  1633. ) -> Any:
  1634. nonlocal has_result
  1635. # simple heuristic to improve reflection performance if a
  1636. # dialect implements multi_reflection:
  1637. # if more than 50% of the tables in the db are in filter_names
  1638. # load all the tables, since it's most likely faster to avoid
  1639. # a filter on that many tables.
  1640. if (
  1641. fraction is None
  1642. or fraction <= 0.5
  1643. or not self.dialect._overrides_default(meth.__name__)
  1644. ):
  1645. _fn = filter_names
  1646. else:
  1647. _fn = None
  1648. try:
  1649. if has_result:
  1650. res = meth(filter_names=_fn, **kw)
  1651. if check_filter_names_from_meth and not res:
  1652. # method returned no result data.
  1653. # skip any future call methods
  1654. has_result = False
  1655. else:
  1656. res = {}
  1657. except NotImplementedError:
  1658. if not optional:
  1659. raise
  1660. res = {}
  1661. return res
  1662. info = _ReflectionInfo(
  1663. columns=run(
  1664. self.get_multi_columns, check_filter_names_from_meth=True
  1665. ),
  1666. pk_constraint=run(self.get_multi_pk_constraint),
  1667. foreign_keys=run(self.get_multi_foreign_keys),
  1668. indexes=run(self.get_multi_indexes),
  1669. unique_constraints=run(
  1670. self.get_multi_unique_constraints, optional=True
  1671. ),
  1672. table_comment=run(self.get_multi_table_comment, optional=True),
  1673. check_constraints=run(
  1674. self.get_multi_check_constraints, optional=True
  1675. ),
  1676. table_options=run(self.get_multi_table_options, optional=True),
  1677. unreflectable=unreflectable,
  1678. )
  1679. if _reflect_info:
  1680. _reflect_info.update(info)
  1681. return _reflect_info
  1682. else:
  1683. return info
  1684. @final
  1685. class ReflectionDefaults:
  1686. """provides blank default values for reflection methods."""
  1687. @classmethod
  1688. def columns(cls) -> List[ReflectedColumn]:
  1689. return []
  1690. @classmethod
  1691. def pk_constraint(cls) -> ReflectedPrimaryKeyConstraint:
  1692. return {
  1693. "name": None,
  1694. "constrained_columns": [],
  1695. }
  1696. @classmethod
  1697. def foreign_keys(cls) -> List[ReflectedForeignKeyConstraint]:
  1698. return []
  1699. @classmethod
  1700. def indexes(cls) -> List[ReflectedIndex]:
  1701. return []
  1702. @classmethod
  1703. def unique_constraints(cls) -> List[ReflectedUniqueConstraint]:
  1704. return []
  1705. @classmethod
  1706. def check_constraints(cls) -> List[ReflectedCheckConstraint]:
  1707. return []
  1708. @classmethod
  1709. def table_options(cls) -> Dict[str, Any]:
  1710. return {}
  1711. @classmethod
  1712. def table_comment(cls) -> ReflectedTableComment:
  1713. return {"text": None}
  1714. @dataclass
  1715. class _ReflectionInfo:
  1716. columns: Dict[TableKey, List[ReflectedColumn]]
  1717. pk_constraint: Dict[TableKey, Optional[ReflectedPrimaryKeyConstraint]]
  1718. foreign_keys: Dict[TableKey, List[ReflectedForeignKeyConstraint]]
  1719. indexes: Dict[TableKey, List[ReflectedIndex]]
  1720. # optionals
  1721. unique_constraints: Dict[TableKey, List[ReflectedUniqueConstraint]]
  1722. table_comment: Dict[TableKey, Optional[ReflectedTableComment]]
  1723. check_constraints: Dict[TableKey, List[ReflectedCheckConstraint]]
  1724. table_options: Dict[TableKey, Dict[str, Any]]
  1725. unreflectable: Dict[TableKey, exc.UnreflectableTableError]
  1726. def update(self, other: _ReflectionInfo) -> None:
  1727. for k, v in self.__dict__.items():
  1728. ov = getattr(other, k)
  1729. if ov is not None:
  1730. if v is None:
  1731. setattr(self, k, ov)
  1732. else:
  1733. v.update(ov)