codekingpro/portable-devtools
115k
1# sql/schema.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""The schema module provides the building blocks for database metadata.
9
10Each element within this module describes a database entity which can be
11created and dropped, or is otherwise part of such an entity. Examples include
12tables, columns, sequences, and indexes.
13
14All entities are subclasses of :class:`~sqlalchemy.schema.SchemaItem`, and as
15defined in this module they are intended to be agnostic of any vendor-specific
16constructs.
17
18A collection of entities are grouped into a unit called
19:class:`~sqlalchemy.schema.MetaData`. MetaData serves as a logical grouping of
20schema elements, and can also be associated with an actual database connection
21such that operations involving the contained elements can contact the database
22as needed.
23
24Two of the elements here also build upon their "syntactic" counterparts, which
25are defined in :class:`~sqlalchemy.sql.expression.`, specifically
26:class:`~sqlalchemy.schema.Table` and :class:`~sqlalchemy.schema.Column`.
27Since these objects are part of the SQL expression language, they are usable
28as components in SQL expressions.
29
30"""
31from __future__ import annotations
32
33from abc import ABC
34import collections
35from enum import Enum
36import operator
37import typing
38from typing import Any
39from typing import Callable
40from typing import cast
41from typing import Collection
42from typing import Dict
43from typing import Iterable
44from typing import Iterator
45from typing import List
46from typing import Mapping
47from typing import NoReturn
48from typing import Optional
49from typing import overload
50from typing import Sequence as _typing_Sequence
51from typing import Set
52from typing import Tuple
53from typing import TYPE_CHECKING
54from typing import TypeVar
55from typing import Union
56
57from . import coercions
58from . import ddl
59from . import roles
60from . import type_api
61from . import visitors
62from .base import _DefaultDescriptionTuple
63from .base import _NoArg
64from .base import _NoneName
65from .base import _SentinelColumnCharacterization
66from .base import _SentinelDefaultCharacterization
67from .base import DedupeColumnCollection
68from .base import DialectKWArgs
69from .base import Executable
70from .base import SchemaEventTarget as SchemaEventTarget
71from .base import SchemaVisitable as SchemaVisitable
72from .coercions import _document_text_coercion
73from .elements import ClauseElement
74from .elements import ColumnClause
75from .elements import ColumnElement
76from .elements import quoted_name
77from .elements import TextClause
78from .selectable import TableClause
79from .type_api import to_instance
80from .visitors import ExternallyTraversible
81from .. import event
82from .. import exc
83from .. import inspection
84from .. import util
85from ..util import HasMemoized
86from ..util.typing import Final
87from ..util.typing import Literal
88from ..util.typing import Protocol
89from ..util.typing import Self
90from ..util.typing import TypedDict
91from ..util.typing import TypeGuard
92
93if typing.TYPE_CHECKING:
94 from ._typing import _AutoIncrementType
95 from ._typing import _CreateDropBind
96 from ._typing import _DDLColumnArgument
97 from ._typing import _InfoType
98 from ._typing import _TextCoercedExpressionArgument
99 from ._typing import _TypeEngineArgument
100 from .base import ColumnSet
101 from .base import ReadOnlyColumnCollection
102 from .compiler import DDLCompiler
103 from .elements import BindParameter
104 from .elements import KeyedColumnElement
105 from .functions import Function
106 from .type_api import TypeEngine
107 from .visitors import anon_map
108 from ..engine import Connection
109 from ..engine import Engine
110 from ..engine.interfaces import _CoreMultiExecuteParams
111 from ..engine.interfaces import CoreExecuteOptionsParameter
112 from ..engine.interfaces import ExecutionContext
113 from ..engine.reflection import _ReflectionInfo
114 from ..sql.selectable import FromClause
115
116_T = TypeVar("_T", bound="Any")
117_SI = TypeVar("_SI", bound="SchemaItem")
118_TAB = TypeVar("_TAB", bound="Table")
119
120
121_ConstraintNameArgument = Optional[Union[str, _NoneName]]
122
123_ServerDefaultArgument = Union[
124 "FetchedValue", str, TextClause, ColumnElement[Any]
125]
126
127_ServerOnUpdateArgument = _ServerDefaultArgument
128
129
130class SchemaConst(Enum):
131 RETAIN_SCHEMA = 1
132 """Symbol indicating that a :class:`_schema.Table`, :class:`.Sequence`
133 or in some cases a :class:`_schema.ForeignKey` object, in situations
134 where the object is being copied for a :meth:`.Table.to_metadata`
135 operation, should retain the schema name that it already has.
136
137 """
138
139 BLANK_SCHEMA = 2
140 """Symbol indicating that a :class:`_schema.Table` or :class:`.Sequence`
141 should have 'None' for its schema, even if the parent
142 :class:`_schema.MetaData` has specified a schema.
143
144 .. seealso::
145
146 :paramref:`_schema.MetaData.schema`
147
148 :paramref:`_schema.Table.schema`
149
150 :paramref:`.Sequence.schema`
151
152 """
153
154 NULL_UNSPECIFIED = 3
155 """Symbol indicating the "nullable" keyword was not passed to a Column.
156
157 This is used to distinguish between the use case of passing
158 ``nullable=None`` to a :class:`.Column`, which has special meaning
159 on some backends such as SQL Server.
160
161 """
162
163
164RETAIN_SCHEMA: Final[Literal[SchemaConst.RETAIN_SCHEMA]] = (
165 SchemaConst.RETAIN_SCHEMA
166)
167BLANK_SCHEMA: Final[Literal[SchemaConst.BLANK_SCHEMA]] = (
168 SchemaConst.BLANK_SCHEMA
169)
170NULL_UNSPECIFIED: Final[Literal[SchemaConst.NULL_UNSPECIFIED]] = (
171 SchemaConst.NULL_UNSPECIFIED
172)
173
174
175def _get_table_key(name: str, schema: Optional[str]) -> str:
176 if schema is None:
177 return name
178 else:
179 return schema + "." + name
180
181
182# this should really be in sql/util.py but we'd have to
183# break an import cycle
184def _copy_expression(
185 expression: ColumnElement[Any],
186 source_table: Optional[Table],
187 target_table: Optional[Table],
188) -> ColumnElement[Any]:
189 if source_table is None or target_table is None:
190 return expression
191
192 fixed_source_table = source_table
193 fixed_target_table = target_table
194
195 def replace(
196 element: ExternallyTraversible, **kw: Any
197 ) -> Optional[ExternallyTraversible]:
198 if (
199 isinstance(element, Column)
200 and element.table is fixed_source_table
201 and element.key in fixed_source_table.c
202 ):
203 return fixed_target_table.c[element.key]
204 else:
205 return None
206
207 return cast(
208 ColumnElement[Any],
209 visitors.replacement_traverse(expression, {}, replace),
210 )
211
212
213@inspection._self_inspects
214class SchemaItem(SchemaVisitable):
215 """Base class for items that define a database schema."""
216
217 __visit_name__ = "schema_item"
218
219 create_drop_stringify_dialect = "default"
220
221 def _init_items(self, *args: SchemaItem, **kw: Any) -> None:
222 """Initialize the list of child items for this SchemaItem."""
223 for item in args:
224 if item is not None:
225 try:
226 spwd = item._set_parent_with_dispatch
227 except AttributeError as err:
228 raise exc.ArgumentError(
229 "'SchemaItem' object, such as a 'Column' or a "
230 f"'Constraint' expected, got {item!r}"
231 ) from err
232 else:
233 spwd(self, **kw)
234
235 def __repr__(self) -> str:
236 return util.generic_repr(self, omit_kwarg=["info"])
237
238 @util.memoized_property
239 def info(self) -> _InfoType:
240 """Info dictionary associated with the object, allowing user-defined
241 data to be associated with this :class:`.SchemaItem`.
242
243 The dictionary is automatically generated when first accessed.
244 It can also be specified in the constructor of some objects,
245 such as :class:`_schema.Table` and :class:`_schema.Column`.
246
247 """
248 return {}
249
250 def _schema_item_copy(self, schema_item: _SI) -> _SI:
251 if "info" in self.__dict__:
252 schema_item.info = self.info.copy()
253 schema_item.dispatch._update(self.dispatch)
254 return schema_item
255
256 _use_schema_map = True
257
258
259class HasConditionalDDL:
260 """define a class that includes the :meth:`.HasConditionalDDL.ddl_if`
261 method, allowing for conditional rendering of DDL.
262
263 Currently applies to constraints and indexes.
264
265 .. versionadded:: 2.0
266
267
268 """
269
270 _ddl_if: Optional[ddl.DDLIf] = None
271
272 def ddl_if(
273 self,
274 dialect: Optional[str] = None,
275 callable_: Optional[ddl.DDLIfCallable] = None,
276 state: Optional[Any] = None,
277 ) -> Self:
278 r"""apply a conditional DDL rule to this schema item.
279
280 These rules work in a similar manner to the
281 :meth:`.ExecutableDDLElement.execute_if` callable, with the added
282 feature that the criteria may be checked within the DDL compilation
283 phase for a construct such as :class:`.CreateTable`.
284 :meth:`.HasConditionalDDL.ddl_if` currently applies towards the
285 :class:`.Index` construct as well as all :class:`.Constraint`
286 constructs.
287
288 :param dialect: string name of a dialect, or a tuple of string names
289 to indicate multiple dialect types.
290
291 :param callable\_: a callable that is constructed using the same form
292 as that described in
293 :paramref:`.ExecutableDDLElement.execute_if.callable_`.
294
295 :param state: any arbitrary object that will be passed to the
296 callable, if present.
297
298 .. versionadded:: 2.0
299
300 .. seealso::
301
302 :ref:`schema_ddl_ddl_if` - background and usage examples
303
304
305 """
306 self._ddl_if = ddl.DDLIf(dialect, callable_, state)
307 return self
308
309
310class HasSchemaAttr(SchemaItem):
311 """schema item that includes a top-level schema name"""
312
313 schema: Optional[str]
314
315
316class Table(
317 DialectKWArgs, HasSchemaAttr, TableClause, inspection.Inspectable["Table"]
318):
319 r"""Represent a table in a database.
320
321 e.g.::
322
323 mytable = Table(
324 "mytable",
325 metadata,
326 Column("mytable_id", Integer, primary_key=True),
327 Column("value", String(50)),
328 )
329
330 The :class:`_schema.Table`
331 object constructs a unique instance of itself based
332 on its name and optional schema name within the given
333 :class:`_schema.MetaData` object. Calling the :class:`_schema.Table`
334 constructor with the same name and same :class:`_schema.MetaData` argument
335 a second time will return the *same* :class:`_schema.Table`
336 object - in this way
337 the :class:`_schema.Table` constructor acts as a registry function.
338
339 .. seealso::
340
341 :ref:`metadata_describing` - Introduction to database metadata
342
343 """
344
345 __visit_name__ = "table"
346
347 if TYPE_CHECKING:
348
349 @util.ro_non_memoized_property
350 def primary_key(self) -> PrimaryKeyConstraint: ...
351
352 @util.ro_non_memoized_property
353 def foreign_keys(self) -> Set[ForeignKey]: ...
354
355 _columns: DedupeColumnCollection[Column[Any]]
356
357 _sentinel_column: Optional[Column[Any]]
358
359 constraints: Set[Constraint]
360 """A collection of all :class:`_schema.Constraint` objects associated with
361 this :class:`_schema.Table`.
362
363 Includes :class:`_schema.PrimaryKeyConstraint`,
364 :class:`_schema.ForeignKeyConstraint`, :class:`_schema.UniqueConstraint`,
365 :class:`_schema.CheckConstraint`. A separate collection
366 :attr:`_schema.Table.foreign_key_constraints` refers to the collection
367 of all :class:`_schema.ForeignKeyConstraint` objects, and the
368 :attr:`_schema.Table.primary_key` attribute refers to the single
369 :class:`_schema.PrimaryKeyConstraint` associated with the
370 :class:`_schema.Table`.
371
372 .. seealso::
373
374 :attr:`_schema.Table.constraints`
375
376 :attr:`_schema.Table.primary_key`
377
378 :attr:`_schema.Table.foreign_key_constraints`
379
380 :attr:`_schema.Table.indexes`
381
382 :class:`_reflection.Inspector`
383
384
385 """
386
387 indexes: Set[Index]
388 """A collection of all :class:`_schema.Index` objects associated with this
389 :class:`_schema.Table`.
390
391 .. seealso::
392
393 :meth:`_reflection.Inspector.get_indexes`
394
395 """
396
397 if TYPE_CHECKING:
398
399 @util.ro_non_memoized_property
400 def columns(self) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
401
402 @util.ro_non_memoized_property
403 def exported_columns(
404 self,
405 ) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
406
407 @util.ro_non_memoized_property
408 def c(self) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
409
410 def _gen_cache_key(
411 self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
412 ) -> Tuple[Any, ...]:
413 if self._annotations:
414 return (self,) + self._annotations_cache_key
415 else:
416 return (self,)
417
418 if not typing.TYPE_CHECKING:
419 # typing tools seem to be inconsistent in how they handle
420 # __new__, so suggest this pattern for classes that use
421 # __new__. apply typing to the __init__ method normally
422 @util.deprecated_params(
423 mustexist=(
424 "1.4",
425 "Deprecated alias of :paramref:`_schema.Table.must_exist`",
426 ),
427 )
428 def __new__(cls, *args: Any, **kw: Any) -> Any:
429 return cls._new(*args, **kw)
430
431 @classmethod
432 def _new(cls, *args: Any, **kw: Any) -> Any:
433 if not args and not kw:
434 # python3k pickle seems to call this
435 return object.__new__(cls)
436
437 try:
438 name, metadata, args = args[0], args[1], args[2:]
439 except IndexError:
440 raise TypeError(
441 "Table() takes at least two positional-only "
442 "arguments 'name' and 'metadata'"
443 )
444
445 schema = kw.get("schema", None)
446 if schema is None:
447 schema = metadata.schema
448 elif schema is BLANK_SCHEMA:
449 schema = None
450 keep_existing = kw.get("keep_existing", False)
451 extend_existing = kw.get("extend_existing", False)
452
453 if keep_existing and extend_existing:
454 msg = "keep_existing and extend_existing are mutually exclusive."
455 raise exc.ArgumentError(msg)
456
457 must_exist = kw.pop("must_exist", kw.pop("mustexist", False))
458 key = _get_table_key(name, schema)
459 if key in metadata.tables:
460 if not keep_existing and not extend_existing and bool(args):
461 raise exc.InvalidRequestError(
462 f"Table '{key}' is already defined for this MetaData "
463 "instance. Specify 'extend_existing=True' "
464 "to redefine "
465 "options and columns on an "
466 "existing Table object."
467 )
468 table = metadata.tables[key]
469 if extend_existing:
470 table._init_existing(*args, **kw)
471 return table
472 else:
473 if must_exist:
474 raise exc.InvalidRequestError(f"Table '{key}' not defined")
475 table = object.__new__(cls)
476 table.dispatch.before_parent_attach(table, metadata)
477 metadata._add_table(name, schema, table)
478 try:
479 table.__init__(name, metadata, *args, _no_init=False, **kw) # type: ignore[misc] # noqa: E501
480 table.dispatch.after_parent_attach(table, metadata)
481 return table
482 except Exception:
483 with util.safe_reraise():
484 metadata._remove_table(name, schema)
485
486 def __init__(
487 self,
488 name: str,
489 metadata: MetaData,
490 *args: SchemaItem,
491 schema: Optional[Union[str, Literal[SchemaConst.BLANK_SCHEMA]]] = None,
492 quote: Optional[bool] = None,
493 quote_schema: Optional[bool] = None,
494 autoload_with: Optional[Union[Engine, Connection]] = None,
495 autoload_replace: bool = True,
496 keep_existing: bool = False,
497 extend_existing: bool = False,
498 resolve_fks: bool = True,
499 include_columns: Optional[Collection[str]] = None,
500 implicit_returning: bool = True,
501 comment: Optional[str] = None,
502 info: Optional[Dict[Any, Any]] = None,
503 listeners: Optional[
504 _typing_Sequence[Tuple[str, Callable[..., Any]]]
505 ] = None,
506 prefixes: Optional[_typing_Sequence[str]] = None,
507 # used internally in the metadata.reflect() process
508 _extend_on: Optional[Set[Table]] = None,
509 # used by __new__ to bypass __init__
510 _no_init: bool = True,
511 # dialect-specific keyword args
512 **kw: Any,
513 ) -> None:
514 r"""Constructor for :class:`_schema.Table`.
515
516
517 :param name: The name of this table as represented in the database.
518
519 The table name, along with the value of the ``schema`` parameter,
520 forms a key which uniquely identifies this :class:`_schema.Table`
521 within
522 the owning :class:`_schema.MetaData` collection.
523 Additional calls to :class:`_schema.Table` with the same name,
524 metadata,
525 and schema name will return the same :class:`_schema.Table` object.
526
527 Names which contain no upper case characters
528 will be treated as case insensitive names, and will not be quoted
529 unless they are a reserved word or contain special characters.
530 A name with any number of upper case characters is considered
531 to be case sensitive, and will be sent as quoted.
532
533 To enable unconditional quoting for the table name, specify the flag
534 ``quote=True`` to the constructor, or use the :class:`.quoted_name`
535 construct to specify the name.
536
537 :param metadata: a :class:`_schema.MetaData`
538 object which will contain this
539 table. The metadata is used as a point of association of this table
540 with other tables which are referenced via foreign key. It also
541 may be used to associate this table with a particular
542 :class:`.Connection` or :class:`.Engine`.
543
544 :param \*args: Additional positional arguments are used primarily
545 to add the list of :class:`_schema.Column`
546 objects contained within this
547 table. Similar to the style of a CREATE TABLE statement, other
548 :class:`.SchemaItem` constructs may be added here, including
549 :class:`.PrimaryKeyConstraint`, and
550 :class:`_schema.ForeignKeyConstraint`.
551
552 :param autoload_replace: Defaults to ``True``; when using
553 :paramref:`_schema.Table.autoload_with`
554 in conjunction with :paramref:`_schema.Table.extend_existing`,
555 indicates
556 that :class:`_schema.Column` objects present in the already-existing
557 :class:`_schema.Table`
558 object should be replaced with columns of the same
559 name retrieved from the autoload process. When ``False``, columns
560 already present under existing names will be omitted from the
561 reflection process.
562
563 Note that this setting does not impact :class:`_schema.Column` objects
564 specified programmatically within the call to :class:`_schema.Table`
565 that
566 also is autoloading; those :class:`_schema.Column` objects will always
567 replace existing columns of the same name when
568 :paramref:`_schema.Table.extend_existing` is ``True``.
569
570 .. seealso::
571
572 :paramref:`_schema.Table.autoload_with`
573
574 :paramref:`_schema.Table.extend_existing`
575
576 :param autoload_with: An :class:`_engine.Engine` or
577 :class:`_engine.Connection` object,
578 or a :class:`_reflection.Inspector` object as returned by
579 :func:`_sa.inspect`
580 against one, with which this :class:`_schema.Table`
581 object will be reflected.
582 When set to a non-None value, the autoload process will take place
583 for this table against the given engine or connection.
584
585 .. seealso::
586
587 :ref:`metadata_reflection_toplevel`
588
589 :meth:`_events.DDLEvents.column_reflect`
590
591 :ref:`metadata_reflection_dbagnostic_types`
592
593 :param extend_existing: When ``True``, indicates that if this
594 :class:`_schema.Table` is already present in the given
595 :class:`_schema.MetaData`,
596 apply further arguments within the constructor to the existing
597 :class:`_schema.Table`.
598
599 If :paramref:`_schema.Table.extend_existing` or
600 :paramref:`_schema.Table.keep_existing` are not set,
601 and the given name
602 of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
603 that is
604 already present in the target :class:`_schema.MetaData` collection,
605 and
606 this :class:`_schema.Table`
607 specifies additional columns or other constructs
608 or flags that modify the table's state, an
609 error is raised. The purpose of these two mutually-exclusive flags
610 is to specify what action should be taken when a
611 :class:`_schema.Table`
612 is specified that matches an existing :class:`_schema.Table`,
613 yet specifies
614 additional constructs.
615
616 :paramref:`_schema.Table.extend_existing`
617 will also work in conjunction
618 with :paramref:`_schema.Table.autoload_with` to run a new reflection
619 operation against the database, even if a :class:`_schema.Table`
620 of the same name is already present in the target
621 :class:`_schema.MetaData`; newly reflected :class:`_schema.Column`
622 objects
623 and other options will be added into the state of the
624 :class:`_schema.Table`, potentially overwriting existing columns
625 and options of the same name.
626
627 As is always the case with :paramref:`_schema.Table.autoload_with`,
628 :class:`_schema.Column` objects can be specified in the same
629 :class:`_schema.Table`
630 constructor, which will take precedence. Below, the existing
631 table ``mytable`` will be augmented with :class:`_schema.Column`
632 objects
633 both reflected from the database, as well as the given
634 :class:`_schema.Column`
635 named "y"::
636
637 Table(
638 "mytable",
639 metadata,
640 Column("y", Integer),
641 extend_existing=True,
642 autoload_with=engine,
643 )
644
645 .. seealso::
646
647 :paramref:`_schema.Table.autoload_with`
648
649 :paramref:`_schema.Table.autoload_replace`
650
651 :paramref:`_schema.Table.keep_existing`
652
653
654 :param implicit_returning: True by default - indicates that
655 RETURNING can be used, typically by the ORM, in order to fetch
656 server-generated values such as primary key values and
657 server side defaults, on those backends which support RETURNING.
658
659 In modern SQLAlchemy there is generally no reason to alter this
660 setting, except for some backend specific cases
661 (see :ref:`mssql_triggers` in the SQL Server dialect documentation
662 for one such example).
663
664 :param include_columns: A list of strings indicating a subset of
665 columns to be loaded via the ``autoload`` operation; table columns who
666 aren't present in this list will not be represented on the resulting
667 ``Table`` object. Defaults to ``None`` which indicates all columns
668 should be reflected.
669
670 :param resolve_fks: Whether or not to reflect :class:`_schema.Table`
671 objects
672 related to this one via :class:`_schema.ForeignKey` objects, when
673 :paramref:`_schema.Table.autoload_with` is
674 specified. Defaults to True. Set to False to disable reflection of
675 related tables as :class:`_schema.ForeignKey`
676 objects are encountered; may be
677 used either to save on SQL calls or to avoid issues with related tables
678 that can't be accessed. Note that if a related table is already present
679 in the :class:`_schema.MetaData` collection, or becomes present later,
680 a
681 :class:`_schema.ForeignKey` object associated with this
682 :class:`_schema.Table` will
683 resolve to that table normally.
684
685 .. versionadded:: 1.3
686
687 .. seealso::
688
689 :paramref:`.MetaData.reflect.resolve_fks`
690
691
692 :param info: Optional data dictionary which will be populated into the
693 :attr:`.SchemaItem.info` attribute of this object.
694
695 :param keep_existing: When ``True``, indicates that if this Table
696 is already present in the given :class:`_schema.MetaData`, ignore
697 further arguments within the constructor to the existing
698 :class:`_schema.Table`, and return the :class:`_schema.Table`
699 object as
700 originally created. This is to allow a function that wishes
701 to define a new :class:`_schema.Table` on first call, but on
702 subsequent calls will return the same :class:`_schema.Table`,
703 without any of the declarations (particularly constraints)
704 being applied a second time.
705
706 If :paramref:`_schema.Table.extend_existing` or
707 :paramref:`_schema.Table.keep_existing` are not set,
708 and the given name
709 of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
710 that is
711 already present in the target :class:`_schema.MetaData` collection,
712 and
713 this :class:`_schema.Table`
714 specifies additional columns or other constructs
715 or flags that modify the table's state, an
716 error is raised. The purpose of these two mutually-exclusive flags
717 is to specify what action should be taken when a
718 :class:`_schema.Table`
719 is specified that matches an existing :class:`_schema.Table`,
720 yet specifies
721 additional constructs.
722
723 .. seealso::
724
725 :paramref:`_schema.Table.extend_existing`
726
727 :param listeners: A list of tuples of the form ``(<eventname>, <fn>)``
728 which will be passed to :func:`.event.listen` upon construction.
729 This alternate hook to :func:`.event.listen` allows the establishment
730 of a listener function specific to this :class:`_schema.Table` before
731 the "autoload" process begins. Historically this has been intended
732 for use with the :meth:`.DDLEvents.column_reflect` event, however
733 note that this event hook may now be associated with the
734 :class:`_schema.MetaData` object directly::
735
736 def listen_for_reflect(table, column_info):
737 "handle the column reflection event"
738 # ...
739
740
741 t = Table(
742 "sometable",
743 autoload_with=engine,
744 listeners=[("column_reflect", listen_for_reflect)],
745 )
746
747 .. seealso::
748
749 :meth:`_events.DDLEvents.column_reflect`
750
751 :param must_exist: When ``True``, indicates that this Table must already
752 be present in the given :class:`_schema.MetaData` collection, else
753 an exception is raised.
754
755 :param prefixes:
756 A list of strings to insert after CREATE in the CREATE TABLE
757 statement. They will be separated by spaces.
758
759 :param quote: Force quoting of this table's name on or off, corresponding
760 to ``True`` or ``False``. When left at its default of ``None``,
761 the column identifier will be quoted according to whether the name is
762 case sensitive (identifiers with at least one upper case character are
763 treated as case sensitive), or if it's a reserved word. This flag
764 is only needed to force quoting of a reserved word which is not known
765 by the SQLAlchemy dialect.
766
767 .. note:: setting this flag to ``False`` will not provide
768 case-insensitive behavior for table reflection; table reflection
769 will always search for a mixed-case name in a case sensitive
770 fashion. Case insensitive names are specified in SQLAlchemy only
771 by stating the name with all lower case characters.
772
773 :param quote_schema: same as 'quote' but applies to the schema identifier.
774
775 :param schema: The schema name for this table, which is required if
776 the table resides in a schema other than the default selected schema
777 for the engine's database connection. Defaults to ``None``.
778
779 If the owning :class:`_schema.MetaData` of this :class:`_schema.Table`
780 specifies its
781 own :paramref:`_schema.MetaData.schema` parameter,
782 then that schema name will
783 be applied to this :class:`_schema.Table`
784 if the schema parameter here is set
785 to ``None``. To set a blank schema name on a :class:`_schema.Table`
786 that
787 would otherwise use the schema set on the owning
788 :class:`_schema.MetaData`,
789 specify the special symbol :attr:`.BLANK_SCHEMA`.
790
791 The quoting rules for the schema name are the same as those for the
792 ``name`` parameter, in that quoting is applied for reserved words or
793 case-sensitive names; to enable unconditional quoting for the schema
794 name, specify the flag ``quote_schema=True`` to the constructor, or use
795 the :class:`.quoted_name` construct to specify the name.
796
797 :param comment: Optional string that will render an SQL comment on table
798 creation.
799
800 .. versionadded:: 1.2 Added the :paramref:`_schema.Table.comment`
801 parameter
802 to :class:`_schema.Table`.
803
804 :param \**kw: Additional keyword arguments not mentioned above are
805 dialect specific, and passed in the form ``<dialectname>_<argname>``.
806 See the documentation regarding an individual dialect at
807 :ref:`dialect_toplevel` for detail on documented arguments.
808
809 """ # noqa: E501
810 if _no_init:
811 # don't run __init__ from __new__ by default;
812 # __new__ has a specific place that __init__ is called
813 return
814
815 super().__init__(quoted_name(name, quote))
816 self.metadata = metadata
817
818 if schema is None:
819 self.schema = metadata.schema
820 elif schema is BLANK_SCHEMA:
821 self.schema = None
822 else:
823 quote_schema = quote_schema
824 assert isinstance(schema, str)
825 self.schema = quoted_name(schema, quote_schema)
826
827 self._sentinel_column = None
828
829 self.indexes = set()
830 self.constraints = set()
831 PrimaryKeyConstraint(
832 _implicit_generated=True
833 )._set_parent_with_dispatch(self)
834 self.foreign_keys = set() # type: ignore
835 self._extra_dependencies: Set[Table] = set()
836 if self.schema is not None:
837 self.fullname = "%s.%s" % (self.schema, self.name)
838 else:
839 self.fullname = self.name
840
841 self.implicit_returning = implicit_returning
842 _reflect_info = kw.pop("_reflect_info", None)
843
844 self.comment = comment
845
846 if info is not None:
847 self.info = info
848
849 if listeners is not None:
850 for evt, fn in listeners:
851 event.listen(self, evt, fn)
852
853 self._prefixes = prefixes if prefixes else []
854
855 self._extra_kwargs(**kw)
856
857 # load column definitions from the database if 'autoload' is defined
858 # we do it after the table is in the singleton dictionary to support
859 # circular foreign keys
860 if autoload_with is not None:
861 self._autoload(
862 metadata,
863 autoload_with,
864 include_columns,
865 _extend_on=_extend_on,
866 _reflect_info=_reflect_info,
867 resolve_fks=resolve_fks,
868 )
869
870 # initialize all the column, etc. objects. done after reflection to
871 # allow user-overrides
872
873 self._init_items(
874 *args,
875 allow_replacements=extend_existing
876 or keep_existing
877 or autoload_with,
878 all_names={},
879 )
880
881 def _autoload(
882 self,
883 metadata: MetaData,
884 autoload_with: Union[Engine, Connection],
885 include_columns: Optional[Collection[str]],
886 exclude_columns: Collection[str] = (),
887 resolve_fks: bool = True,
888 _extend_on: Optional[Set[Table]] = None,
889 _reflect_info: _ReflectionInfo | None = None,
890 ) -> None:
891 insp = inspection.inspect(autoload_with)
892 with insp._inspection_context() as conn_insp:
893 conn_insp.reflect_table(
894 self,
895 include_columns,
896 exclude_columns,
897 resolve_fks,
898 _extend_on=_extend_on,
899 _reflect_info=_reflect_info,
900 )
901
902 @property
903 def _sorted_constraints(self) -> List[Constraint]:
904 """Return the set of constraints as a list, sorted by creation
905 order.
906
907 """
908
909 return sorted(self.constraints, key=lambda c: c._creation_order)
910
911 @property
912 def foreign_key_constraints(self) -> Set[ForeignKeyConstraint]:
913 """:class:`_schema.ForeignKeyConstraint` objects referred to by this
914 :class:`_schema.Table`.
915
916 This list is produced from the collection of
917 :class:`_schema.ForeignKey`
918 objects currently associated.
919
920
921 .. seealso::
922
923 :attr:`_schema.Table.constraints`
924
925 :attr:`_schema.Table.foreign_keys`
926
927 :attr:`_schema.Table.indexes`
928
929 """
930 return {
931 fkc.constraint
932 for fkc in self.foreign_keys
933 if fkc.constraint is not None
934 }
935
936 def _init_existing(self, *args: Any, **kwargs: Any) -> None:
937 autoload_with = kwargs.pop("autoload_with", None)
938 autoload = kwargs.pop("autoload", autoload_with is not None)
939 autoload_replace = kwargs.pop("autoload_replace", True)
940 schema = kwargs.pop("schema", None)
941 _extend_on = kwargs.pop("_extend_on", None)
942 _reflect_info = kwargs.pop("_reflect_info", None)
943
944 # these arguments are only used with _init()
945 extend_existing = kwargs.pop("extend_existing", False)
946 keep_existing = kwargs.pop("keep_existing", False)
947
948 assert extend_existing
949 assert not keep_existing
950
951 if schema and schema != self.schema:
952 raise exc.ArgumentError(
953 f"Can't change schema of existing table "
954 f"from '{self.schema}' to '{schema}'",
955 )
956
957 include_columns = kwargs.pop("include_columns", None)
958 if include_columns is not None:
959 for c in self.c:
960 if c.name not in include_columns:
961 self._columns.remove(c)
962
963 resolve_fks = kwargs.pop("resolve_fks", True)
964
965 for key in ("quote", "quote_schema"):
966 if key in kwargs:
967 raise exc.ArgumentError(
968 "Can't redefine 'quote' or 'quote_schema' arguments"
969 )
970
971 # update `self` with these kwargs, if provided
972 self.comment = kwargs.pop("comment", self.comment)
973 self.implicit_returning = kwargs.pop(
974 "implicit_returning", self.implicit_returning
975 )
976 self.info = kwargs.pop("info", self.info)
977
978 exclude_columns: _typing_Sequence[str]
979
980 if autoload:
981 if not autoload_replace:
982 # don't replace columns already present.
983 # we'd like to do this for constraints also however we don't
984 # have simple de-duping for unnamed constraints.
985 exclude_columns = [c.name for c in self.c]
986 else:
987 exclude_columns = ()
988 self._autoload(
989 self.metadata,
990 autoload_with,
991 include_columns,
992 exclude_columns,
993 resolve_fks,
994 _extend_on=_extend_on,
995 _reflect_info=_reflect_info,
996 )
997
998 all_names = {c.name: c for c in self.c}
999 self._extra_kwargs(**kwargs)
1000 self._init_items(*args, allow_replacements=True, all_names=all_names)
1001
1002 def _extra_kwargs(self, **kwargs: Any) -> None:
1003 self._validate_dialect_kwargs(kwargs)
1004
1005 def _init_collections(self) -> None:
1006 pass
1007
1008 def _reset_exported(self) -> None:
1009 pass
1010
1011 @util.ro_non_memoized_property
1012 def _autoincrement_column(self) -> Optional[Column[int]]:
1013 return self.primary_key._autoincrement_column
1014
1015 @util.ro_memoized_property
1016 def _sentinel_column_characteristics(
1017 self,
1018 ) -> _SentinelColumnCharacterization:
1019 """determine a candidate column (or columns, in case of a client
1020 generated composite primary key) which can be used as an
1021 "insert sentinel" for an INSERT statement.
1022
1023 The returned structure, :class:`_SentinelColumnCharacterization`,
1024 includes all the details needed by :class:`.Dialect` and
1025 :class:`.SQLCompiler` to determine if these column(s) can be used
1026 as an INSERT..RETURNING sentinel for a particular database
1027 dialect.
1028
1029 .. versionadded:: 2.0.10
1030
1031 """
1032
1033 sentinel_is_explicit = False
1034 sentinel_is_autoinc = False
1035 the_sentinel: Optional[_typing_Sequence[Column[Any]]] = None
1036
1037 # see if a column was explicitly marked "insert_sentinel=True".
1038 explicit_sentinel_col = self._sentinel_column
1039
1040 if explicit_sentinel_col is not None:
1041 the_sentinel = (explicit_sentinel_col,)
1042 sentinel_is_explicit = True
1043
1044 autoinc_col = self._autoincrement_column
1045 if sentinel_is_explicit and explicit_sentinel_col is autoinc_col:
1046 assert autoinc_col is not None
1047 sentinel_is_autoinc = True
1048 elif explicit_sentinel_col is None and autoinc_col is not None:
1049 the_sentinel = (autoinc_col,)
1050 sentinel_is_autoinc = True
1051
1052 default_characterization = _SentinelDefaultCharacterization.UNKNOWN
1053
1054 if the_sentinel:
1055 the_sentinel_zero = the_sentinel[0]
1056 if the_sentinel_zero.identity:
1057 if the_sentinel_zero.identity._increment_is_negative:
1058 if sentinel_is_explicit:
1059 raise exc.InvalidRequestError(
1060 "Can't use IDENTITY default with negative "
1061 "increment as an explicit sentinel column"
1062 )
1063 else:
1064 if sentinel_is_autoinc:
1065 autoinc_col = None
1066 sentinel_is_autoinc = False
1067 the_sentinel = None
1068 else:
1069 default_characterization = (
1070 _SentinelDefaultCharacterization.IDENTITY
1071 )
1072 elif (
1073 the_sentinel_zero.default is None
1074 and the_sentinel_zero.server_default is None
1075 ):
1076 if the_sentinel_zero.nullable:
1077 raise exc.InvalidRequestError(
1078 f"Column {the_sentinel_zero} has been marked as a "
1079 "sentinel "
1080 "column with no default generation function; it "
1081 "at least needs to be marked nullable=False assuming "
1082 "user-populated sentinel values will be used."
1083 )
1084 default_characterization = (
1085 _SentinelDefaultCharacterization.NONE
1086 )
1087 elif the_sentinel_zero.default is not None:
1088 if the_sentinel_zero.default.is_sentinel:
1089 default_characterization = (
1090 _SentinelDefaultCharacterization.SENTINEL_DEFAULT
1091 )
1092 elif default_is_sequence(the_sentinel_zero.default):
1093 if the_sentinel_zero.default._increment_is_negative:
1094 if sentinel_is_explicit:
1095 raise exc.InvalidRequestError(
1096 "Can't use SEQUENCE default with negative "
1097 "increment as an explicit sentinel column"
1098 )
1099 else:
1100 if sentinel_is_autoinc:
1101 autoinc_col = None
1102 sentinel_is_autoinc = False
1103 the_sentinel = None
1104
1105 default_characterization = (
1106 _SentinelDefaultCharacterization.SEQUENCE
1107 )
1108 elif the_sentinel_zero.default.is_callable:
1109 default_characterization = (
1110 _SentinelDefaultCharacterization.CLIENTSIDE
1111 )
1112 elif the_sentinel_zero.server_default is not None:
1113 if sentinel_is_explicit:
1114 raise exc.InvalidRequestError(
1115 f"Column {the_sentinel[0]} can't be a sentinel column "
1116 "because it uses an explicit server side default "
1117 "that's not the Identity() default."
1118 )
1119
1120 default_characterization = (
1121 _SentinelDefaultCharacterization.SERVERSIDE
1122 )
1123
1124 if the_sentinel is None and self.primary_key:
1125 assert autoinc_col is None
1126
1127 # determine for non-autoincrement pk if all elements are
1128 # client side
1129 for _pkc in self.primary_key:
1130 if _pkc.server_default is not None or (
1131 _pkc.default and not _pkc.default.is_callable
1132 ):
1133 break
1134 else:
1135 the_sentinel = tuple(self.primary_key)
1136 default_characterization = (
1137 _SentinelDefaultCharacterization.CLIENTSIDE
1138 )
1139
1140 return _SentinelColumnCharacterization(
1141 the_sentinel,
1142 sentinel_is_explicit,
1143 sentinel_is_autoinc,
1144 default_characterization,
1145 )
1146
1147 @property
1148 def autoincrement_column(self) -> Optional[Column[int]]:
1149 """Returns the :class:`.Column` object which currently represents
1150 the "auto increment" column, if any, else returns None.
1151
1152 This is based on the rules for :class:`.Column` as defined by the
1153 :paramref:`.Column.autoincrement` parameter, which generally means the
1154 column within a single integer column primary key constraint that is
1155 not constrained by a foreign key. If the table does not have such
1156 a primary key constraint, then there's no "autoincrement" column.
1157 A :class:`.Table` may have only one column defined as the
1158 "autoincrement" column.
1159
1160 .. versionadded:: 2.0.4
1161
1162 .. seealso::
1163
1164 :paramref:`.Column.autoincrement`
1165
1166 """
1167 return self._autoincrement_column
1168
1169 @property
1170 def key(self) -> str:
1171 """Return the 'key' for this :class:`_schema.Table`.
1172
1173 This value is used as the dictionary key within the
1174 :attr:`_schema.MetaData.tables` collection. It is typically the same
1175 as that of :attr:`_schema.Table.name` for a table with no
1176 :attr:`_schema.Table.schema`
1177 set; otherwise it is typically of the form
1178 ``schemaname.tablename``.
1179
1180 """
1181 return _get_table_key(self.name, self.schema)
1182
1183 def __repr__(self) -> str:
1184 return "Table(%s)" % ", ".join(
1185 [repr(self.name)]
1186 + [repr(self.metadata)]
1187 + [repr(x) for x in self.columns]
1188 + ["%s=%s" % (k, repr(getattr(self, k))) for k in ["schema"]]
1189 )
1190
1191 def __str__(self) -> str:
1192 return _get_table_key(self.description, self.schema)
1193
1194 def add_is_dependent_on(self, table: Table) -> None:
1195 """Add a 'dependency' for this Table.
1196
1197 This is another Table object which must be created
1198 first before this one can, or dropped after this one.
1199
1200 Usually, dependencies between tables are determined via
