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