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