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