reflection.py 73 KB

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