summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-01-24 17:04:27 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-02-13 14:23:04 -0500
commite545298e35ea9f126054b337e4b5ba01988b29f7 (patch)
treee64aea159111d5921ff01f08b1c4efb667249dfe /lib/sqlalchemy/sql
parentf1da1623b800cd4de3b71fd1b2ad5ccfde286780 (diff)
downloadsqlalchemy-e545298e35ea9f126054b337e4b5ba01988b29f7.tar.gz
establish mypy / typing approach for v2.0
large patch to get ORM / typing efforts started. this is to support adding new test cases to mypy, support dropping sqlalchemy2-stubs entirely from the test suite, validate major ORM typing reorganization to eliminate the need for the mypy plugin. * New declarative approach which uses annotation introspection, fixes: #7535 * Mapped[] is now at the base of all ORM constructs that find themselves in classes, to support direct typing without plugins * Mypy plugin updated for new typing structures * Mypy test suite broken out into "plugin" tests vs. "plain" tests, and enhanced to better support test structures where we assert that various objects are introspected by the type checker as we expect. as we go forward with typing, we will add new use cases to "plain" where we can assert that types are introspected as we expect. * For typing support, users will be much more exposed to the class names of things. Add these all to "sqlalchemy" import space. * Column(ForeignKey()) no longer needs to be `@declared_attr` if the FK refers to a remote table * composite() attributes mapped to a dataclass no longer need to implement a `__composite_values__()` method * with_variant() accepts multiple dialect names Change-Id: I22797c0be73a8fbbd2d6f5e0c0b7258b17fe145d Fixes: #7535 Fixes: #7551 References: #6810
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/__init__.py1
-rw-r--r--lib/sqlalchemy/sql/_selectable_constructors.py8
-rw-r--r--lib/sqlalchemy/sql/_typing.py12
-rw-r--r--lib/sqlalchemy/sql/base.py6
-rw-r--r--lib/sqlalchemy/sql/compiler.py2
-rw-r--r--lib/sqlalchemy/sql/ddl.py20
-rw-r--r--lib/sqlalchemy/sql/expression.py1
-rw-r--r--lib/sqlalchemy/sql/naming.py2
-rw-r--r--lib/sqlalchemy/sql/roles.py29
-rw-r--r--lib/sqlalchemy/sql/schema.py744
-rw-r--r--lib/sqlalchemy/sql/selectable.py67
-rw-r--r--lib/sqlalchemy/sql/type_api.py34
12 files changed, 518 insertions, 408 deletions
diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py
index 2f84370aa..169ddf3db 100644
--- a/lib/sqlalchemy/sql/__init__.py
+++ b/lib/sqlalchemy/sql/__init__.py
@@ -75,6 +75,7 @@ from .expression import quoted_name as quoted_name
from .expression import Select as Select
from .expression import select as select
from .expression import Selectable as Selectable
+from .expression import SelectLabelStyle as SelectLabelStyle
from .expression import StatementLambdaElement as StatementLambdaElement
from .expression import Subquery as Subquery
from .expression import table as table
diff --git a/lib/sqlalchemy/sql/_selectable_constructors.py b/lib/sqlalchemy/sql/_selectable_constructors.py
index 4b67c12f0..d3cf207da 100644
--- a/lib/sqlalchemy/sql/_selectable_constructors.py
+++ b/lib/sqlalchemy/sql/_selectable_constructors.py
@@ -6,11 +6,11 @@
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from typing import Any
-from typing import Type
from typing import Union
from . import coercions
from . import roles
+from ._typing import _ColumnsClauseElement
from .elements import ColumnClause
from .selectable import Alias
from .selectable import CompoundSelect
@@ -21,6 +21,8 @@ from .selectable import Select
from .selectable import TableClause
from .selectable import TableSample
from .selectable import Values
+from ..util.typing import _LiteralStar
+from ..util.typing import Literal
def alias(selectable, name=None, flat=False):
@@ -279,7 +281,9 @@ def outerjoin(left, right, onclause=None, full=False):
return Join(left, right, onclause, isouter=True, full=full)
-def select(*entities: Union[roles.ColumnsClauseRole, Type]) -> "Select":
+def select(
+ *entities: Union[_LiteralStar, Literal[1], _ColumnsClauseElement]
+) -> "Select":
r"""Construct a new :class:`_expression.Select`.
diff --git a/lib/sqlalchemy/sql/_typing.py b/lib/sqlalchemy/sql/_typing.py
index b5b0efb21..4d2dd2688 100644
--- a/lib/sqlalchemy/sql/_typing.py
+++ b/lib/sqlalchemy/sql/_typing.py
@@ -1,9 +1,21 @@
from typing import Any
from typing import Mapping
from typing import Sequence
+from typing import Type
from typing import Union
+from . import roles
+from ..inspection import Inspectable
+from ..util import immutabledict
+
_SingleExecuteParams = Mapping[str, Any]
_MultiExecuteParams = Sequence[_SingleExecuteParams]
_ExecuteParams = Union[_SingleExecuteParams, _MultiExecuteParams]
_ExecuteOptions = Mapping[str, Any]
+_ImmutableExecuteOptions = immutabledict[str, Any]
+_ColumnsClauseElement = Union[
+ roles.ColumnsClauseRole, Type, Inspectable[roles.HasClauseElement]
+]
+_FromClauseElement = Union[
+ roles.FromClauseRole, Type, Inspectable[roles.HasFromClauseElement]
+]
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index f4fe7afab..5828f9369 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -21,6 +21,7 @@ from typing import TypeVar
from . import roles
from . import visitors
+from ._typing import _ImmutableExecuteOptions
from .cache_key import HasCacheKey # noqa
from .cache_key import MemoizedHasCacheKey # noqa
from .traversals import HasCopyInternals # noqa
@@ -832,9 +833,8 @@ class Executable(roles.StatementRole, Generative):
"""
- supports_execution = True
- _execution_options = util.immutabledict()
- _bind = None
+ supports_execution: bool = True
+ _execution_options: _ImmutableExecuteOptions = util.immutabledict()
_with_options = ()
_with_context_options = ()
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 9cf4d8397..bf78b4231 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -889,7 +889,7 @@ class SQLCompiler(Compiled):
def _apply_numbered_params(self):
poscount = itertools.count(1)
self.string = re.sub(
- r"\[_POSITION\]", lambda m: str(util.next(poscount)), self.string
+ r"\[_POSITION\]", lambda m: str(next(poscount)), self.string
)
@util.memoized_property
diff --git a/lib/sqlalchemy/sql/ddl.py b/lib/sqlalchemy/sql/ddl.py
index 18931ce67..f622023b0 100644
--- a/lib/sqlalchemy/sql/ddl.py
+++ b/lib/sqlalchemy/sql/ddl.py
@@ -10,6 +10,11 @@ to invoke them for a create/drop call.
"""
import typing
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
from . import roles
from .base import _generative
@@ -21,6 +26,11 @@ from .. import util
from ..util import topological
+if typing.TYPE_CHECKING:
+ from .schema import ForeignKeyConstraint
+ from .schema import Table
+
+
class _DDLCompiles(ClauseElement):
_hierarchy_supports_caching = False
"""disable cache warnings for all _DDLCompiles subclasses. """
@@ -1007,10 +1017,10 @@ class SchemaDropper(DDLBase):
def sort_tables(
- tables,
- skip_fn=None,
- extra_dependencies=None,
-):
+ tables: Sequence["Table"],
+ skip_fn: Optional[Callable[["ForeignKeyConstraint"], bool]] = None,
+ extra_dependencies: Optional[Sequence[Tuple["Table", "Table"]]] = None,
+) -> List["Table"]:
"""Sort a collection of :class:`_schema.Table` objects based on
dependency.
@@ -1051,7 +1061,7 @@ def sort_tables(
:param tables: a sequence of :class:`_schema.Table` objects.
:param skip_fn: optional callable which will be passed a
- :class:`_schema.ForeignKey` object; if it returns True, this
+ :class:`_schema.ForeignKeyConstraint` object; if it returns True, this
constraint will not be considered as a dependency. Note this is
**different** from the same parameter in
:func:`.sort_tables_and_constraints`, which is
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 0ed5bd986..22195cd7c 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -136,6 +136,7 @@ from .selectable import ScalarSelect as ScalarSelect
from .selectable import Select as Select
from .selectable import Selectable as Selectable
from .selectable import SelectBase as SelectBase
+from .selectable import SelectLabelStyle as SelectLabelStyle
from .selectable import Subquery as Subquery
from .selectable import TableClause as TableClause
from .selectable import TableSample as TableSample
diff --git a/lib/sqlalchemy/sql/naming.py b/lib/sqlalchemy/sql/naming.py
index 00a2b1d89..15a1566a6 100644
--- a/lib/sqlalchemy/sql/naming.py
+++ b/lib/sqlalchemy/sql/naming.py
@@ -14,7 +14,7 @@ import re
from . import events # noqa
from .elements import _NONE_NAME
-from .elements import conv
+from .elements import conv as conv
from .schema import CheckConstraint
from .schema import Column
from .schema import Constraint
diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py
index 787a1c25e..b41ef7a5d 100644
--- a/lib/sqlalchemy/sql/roles.py
+++ b/lib/sqlalchemy/sql/roles.py
@@ -4,10 +4,17 @@
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
+import typing
+from sqlalchemy.util.langhelpers import TypingOnly
from .. import util
+if typing.TYPE_CHECKING:
+ from .elements import ClauseElement
+ from .selectable import FromClause
+
+
class SQLRole:
"""Define a "role" within a SQL statement structure.
@@ -284,3 +291,25 @@ class DDLReferredColumnRole(DDLConstraintColumnRole):
_role_name = (
"String column name or Column object for DDL foreign key constraint"
)
+
+
+class HasClauseElement(TypingOnly):
+ """indicates a class that has a __clause_element__() method"""
+
+ __slots__ = ()
+
+ if typing.TYPE_CHECKING:
+
+ def __clause_element__(self) -> "ClauseElement":
+ ...
+
+
+class HasFromClauseElement(HasClauseElement, TypingOnly):
+ """indicates a class that has a __clause_element__() method"""
+
+ __slots__ = ()
+
+ if typing.TYPE_CHECKING:
+
+ def __clause_element__(self) -> "FromClause":
+ ...
diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py
index a04fad05d..9387ae030 100644
--- a/lib/sqlalchemy/sql/schema.py
+++ b/lib/sqlalchemy/sql/schema.py
@@ -31,9 +31,12 @@ as components in SQL expressions.
import collections
import typing
from typing import Any
+from typing import Dict
+from typing import List
from typing import MutableMapping
from typing import Optional
from typing import overload
+from typing import Sequence as _typing_Sequence
from typing import Type
from typing import TypeVar
from typing import Union
@@ -52,6 +55,7 @@ from .elements import ClauseElement
from .elements import ColumnClause
from .elements import ColumnElement
from .elements import quoted_name
+from .elements import SQLCoreOperations
from .elements import TextClause
from .selectable import TableClause
from .type_api import to_instance
@@ -64,9 +68,12 @@ from ..util.typing import Literal
if typing.TYPE_CHECKING:
from .type_api import TypeEngine
+ from ..engine import Connection
+ from ..engine import Engine
_T = TypeVar("_T", bound="Any")
_ServerDefaultType = Union["FetchedValue", str, TextClause, ColumnElement]
+_TAB = TypeVar("_TAB", bound="Table")
RETAIN_SCHEMA = util.symbol("retain_schema")
@@ -188,313 +195,6 @@ class Table(DialectKWArgs, SchemaItem, TableClause):
:ref:`metadata_describing` - Introduction to database metadata
- Constructor arguments are as follows:
-
- :param name: The name of this table as represented in the database.
-
- The table name, along with the value of the ``schema`` parameter,
- forms a key which uniquely identifies this :class:`_schema.Table`
- within
- the owning :class:`_schema.MetaData` collection.
- Additional calls to :class:`_schema.Table` with the same name,
- metadata,
- and schema name will return the same :class:`_schema.Table` object.
-
- Names which contain no upper case characters
- will be treated as case insensitive names, and will not be quoted
- unless they are a reserved word or contain special characters.
- A name with any number of upper case characters is considered
- to be case sensitive, and will be sent as quoted.
-
- To enable unconditional quoting for the table name, specify the flag
- ``quote=True`` to the constructor, or use the :class:`.quoted_name`
- construct to specify the name.
-
- :param metadata: a :class:`_schema.MetaData`
- object which will contain this
- table. The metadata is used as a point of association of this table
- with other tables which are referenced via foreign key. It also
- may be used to associate this table with a particular
- :class:`.Connection` or :class:`.Engine`.
-
- :param \*args: Additional positional arguments are used primarily
- to add the list of :class:`_schema.Column`
- objects contained within this
- table. Similar to the style of a CREATE TABLE statement, other
- :class:`.SchemaItem` constructs may be added here, including
- :class:`.PrimaryKeyConstraint`, and
- :class:`_schema.ForeignKeyConstraint`.
-
- :param autoload: Defaults to ``False``, unless
- :paramref:`_schema.Table.autoload_with`
- is set in which case it defaults to ``True``;
- :class:`_schema.Column` objects
- for this table should be reflected from the database, possibly
- augmenting objects that were explicitly specified.
- :class:`_schema.Column` and other objects explicitly set on the
- table will replace corresponding reflected objects.
-
- .. deprecated:: 1.4
-
- The autoload parameter is deprecated and will be removed in
- version 2.0. Please use the
- :paramref:`_schema.Table.autoload_with` parameter, passing an
- engine or connection.
-
- .. seealso::
-
- :ref:`metadata_reflection_toplevel`
-
- :param autoload_replace: Defaults to ``True``; when using
- :paramref:`_schema.Table.autoload`
- in conjunction with :paramref:`_schema.Table.extend_existing`,
- indicates
- that :class:`_schema.Column` objects present in the already-existing
- :class:`_schema.Table`
- object should be replaced with columns of the same
- name retrieved from the autoload process. When ``False``, columns
- already present under existing names will be omitted from the
- reflection process.
-
- Note that this setting does not impact :class:`_schema.Column` objects
- specified programmatically within the call to :class:`_schema.Table`
- that
- also is autoloading; those :class:`_schema.Column` objects will always
- replace existing columns of the same name when
- :paramref:`_schema.Table.extend_existing` is ``True``.
-
- .. seealso::
-
- :paramref:`_schema.Table.autoload`
-
- :paramref:`_schema.Table.extend_existing`
-
- :param autoload_with: An :class:`_engine.Engine` or
- :class:`_engine.Connection` object,
- or a :class:`_reflection.Inspector` object as returned by
- :func:`_sa.inspect`
- against one, with which this :class:`_schema.Table`
- object will be reflected.
- When set to a non-None value, the autoload process will take place
- for this table against the given engine or connection.
-
- :param extend_existing: When ``True``, indicates that if this
- :class:`_schema.Table` is already present in the given
- :class:`_schema.MetaData`,
- apply further arguments within the constructor to the existing
- :class:`_schema.Table`.
-
- If :paramref:`_schema.Table.extend_existing` or
- :paramref:`_schema.Table.keep_existing` are not set,
- and the given name
- of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
- that is
- already present in the target :class:`_schema.MetaData` collection,
- and
- this :class:`_schema.Table`
- specifies additional columns or other constructs
- or flags that modify the table's state, an
- error is raised. The purpose of these two mutually-exclusive flags
- is to specify what action should be taken when a
- :class:`_schema.Table`
- is specified that matches an existing :class:`_schema.Table`,
- yet specifies
- additional constructs.
-
- :paramref:`_schema.Table.extend_existing`
- will also work in conjunction
- with :paramref:`_schema.Table.autoload` to run a new reflection
- operation against the database, even if a :class:`_schema.Table`
- of the same name is already present in the target
- :class:`_schema.MetaData`; newly reflected :class:`_schema.Column`
- objects
- and other options will be added into the state of the
- :class:`_schema.Table`, potentially overwriting existing columns
- and options of the same name.
-
- As is always the case with :paramref:`_schema.Table.autoload`,
- :class:`_schema.Column` objects can be specified in the same
- :class:`_schema.Table`
- constructor, which will take precedence. Below, the existing
- table ``mytable`` will be augmented with :class:`_schema.Column`
- objects
- both reflected from the database, as well as the given
- :class:`_schema.Column`
- named "y"::
-
- Table("mytable", metadata,
- Column('y', Integer),
- extend_existing=True,
- autoload_with=engine
- )
-
- .. seealso::
-
- :paramref:`_schema.Table.autoload`
-
- :paramref:`_schema.Table.autoload_replace`
-
- :paramref:`_schema.Table.keep_existing`
-
-
- :param implicit_returning: True by default - indicates that
- RETURNING can be used by default to fetch newly inserted primary key
- values, for backends which support this. Note that
- :func:`_sa.create_engine` also provides an ``implicit_returning``
- flag.
-
- :param include_columns: A list of strings indicating a subset of
- columns to be loaded via the ``autoload`` operation; table columns who
- aren't present in this list will not be represented on the resulting
- ``Table`` object. Defaults to ``None`` which indicates all columns
- should be reflected.
-
- :param resolve_fks: Whether or not to reflect :class:`_schema.Table`
- objects
- related to this one via :class:`_schema.ForeignKey` objects, when
- :paramref:`_schema.Table.autoload` or
- :paramref:`_schema.Table.autoload_with` is
- specified. Defaults to True. Set to False to disable reflection of
- related tables as :class:`_schema.ForeignKey`
- objects are encountered; may be
- used either to save on SQL calls or to avoid issues with related tables
- that can't be accessed. Note that if a related table is already present
- in the :class:`_schema.MetaData` collection, or becomes present later,
- a
- :class:`_schema.ForeignKey` object associated with this
- :class:`_schema.Table` will
- resolve to that table normally.
-
- .. versionadded:: 1.3
-
- .. seealso::
-
- :paramref:`.MetaData.reflect.resolve_fks`
-
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.SchemaItem.info` attribute of this object.
-
- :param keep_existing: When ``True``, indicates that if this Table
- is already present in the given :class:`_schema.MetaData`, ignore
- further arguments within the constructor to the existing
- :class:`_schema.Table`, and return the :class:`_schema.Table`
- object as
- originally created. This is to allow a function that wishes
- to define a new :class:`_schema.Table` on first call, but on
- subsequent calls will return the same :class:`_schema.Table`,
- without any of the declarations (particularly constraints)
- being applied a second time.
-
- If :paramref:`_schema.Table.extend_existing` or
- :paramref:`_schema.Table.keep_existing` are not set,
- and the given name
- of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
- that is
- already present in the target :class:`_schema.MetaData` collection,
- and
- this :class:`_schema.Table`
- specifies additional columns or other constructs
- or flags that modify the table's state, an
- error is raised. The purpose of these two mutually-exclusive flags
- is to specify what action should be taken when a
- :class:`_schema.Table`
- is specified that matches an existing :class:`_schema.Table`,
- yet specifies
- additional constructs.
-
- .. seealso::
-
- :paramref:`_schema.Table.extend_existing`
-
- :param listeners: A list of tuples of the form ``(<eventname>, <fn>)``
- which will be passed to :func:`.event.listen` upon construction.
- This alternate hook to :func:`.event.listen` allows the establishment
- of a listener function specific to this :class:`_schema.Table` before
- the "autoload" process begins. Historically this has been intended
- for use with the :meth:`.DDLEvents.column_reflect` event, however
- note that this event hook may now be associated with the
- :class:`_schema.MetaData` object directly::
-
- def listen_for_reflect(table, column_info):
- "handle the column reflection event"
- # ...
-
- t = Table(
- 'sometable',
- autoload_with=engine,
- listeners=[
- ('column_reflect', listen_for_reflect)
- ])
-
- .. seealso::
-
- :meth:`_events.DDLEvents.column_reflect`
-
- :param must_exist: When ``True``, indicates that this Table must already
- be present in the given :class:`_schema.MetaData` collection, else
- an exception is raised.
-
- :param prefixes:
- A list of strings to insert after CREATE in the CREATE TABLE
- statement. They will be separated by spaces.
-
- :param quote: Force quoting of this table's name on or off, corresponding
- to ``True`` or ``False``. When left at its default of ``None``,
- the column identifier will be quoted according to whether the name is
- case sensitive (identifiers with at least one upper case character are
- treated as case sensitive), or if it's a reserved word. This flag
- is only needed to force quoting of a reserved word which is not known
- by the SQLAlchemy dialect.
-
- .. note:: setting this flag to ``False`` will not provide
- case-insensitive behavior for table reflection; table reflection
- will always search for a mixed-case name in a case sensitive
- fashion. Case insensitive names are specified in SQLAlchemy only
- by stating the name with all lower case characters.
-
- :param quote_schema: same as 'quote' but applies to the schema identifier.
-
- :param schema: The schema name for this table, which is required if
- the table resides in a schema other than the default selected schema
- for the engine's database connection. Defaults to ``None``.
-
- If the owning :class:`_schema.MetaData` of this :class:`_schema.Table`
- specifies its
- own :paramref:`_schema.MetaData.schema` parameter,
- then that schema name will
- be applied to this :class:`_schema.Table`
- if the schema parameter here is set
- to ``None``. To set a blank schema name on a :class:`_schema.Table`
- that
- would otherwise use the schema set on the owning
- :class:`_schema.MetaData`,
- specify the special symbol :attr:`.BLANK_SCHEMA`.
-
- .. versionadded:: 1.0.14 Added the :attr:`.BLANK_SCHEMA` symbol to
- allow a :class:`_schema.Table`
- to have a blank schema name even when the
- parent :class:`_schema.MetaData` specifies
- :paramref:`_schema.MetaData.schema`.
-
- The quoting rules for the schema name are the same as those for the
- ``name`` parameter, in that quoting is applied for reserved words or
- case-sensitive names; to enable unconditional quoting for the schema
- name, specify the flag ``quote_schema=True`` to the constructor, or use
- the :class:`.quoted_name` construct to specify the name.
-
- :param comment: Optional string that will render an SQL comment on table
- creation.
-
- .. versionadded:: 1.2 Added the :paramref:`_schema.Table.comment`
- parameter
- to :class:`_schema.Table`.
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form ``<dialectname>_<argname>``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
"""
__visit_name__ = "table"
@@ -547,13 +247,21 @@ class Table(DialectKWArgs, SchemaItem, TableClause):
else:
return (self,)
- @util.deprecated_params(
- mustexist=(
- "1.4",
- "Deprecated alias of :paramref:`_schema.Table.must_exist`",
- ),
- )
- def __new__(cls, *args, **kw):
+ if not typing.TYPE_CHECKING:
+ # typing tools seem to be inconsistent in how they handle
+ # __new__, so suggest this pattern for classes that use
+ # __new__. apply typing to the __init__ method normally
+ @util.deprecated_params(
+ mustexist=(
+ "1.4",
+ "Deprecated alias of :paramref:`_schema.Table.must_exist`",
+ ),
+ )
+ def __new__(cls, *args: Any, **kw: Any) -> Any:
+ return cls._new(*args, **kw)
+
+ @classmethod
+ def _new(cls, *args, **kw):
if not args and not kw:
# python3k pickle seems to call this
return object.__new__(cls)
@@ -607,14 +315,323 @@ class Table(DialectKWArgs, SchemaItem, TableClause):
with util.safe_reraise():
metadata._remove_table(name, schema)
- def __init__(self, *args, **kw):
- """Constructor for :class:`_schema.Table`.
+ def __init__(
+ self,
+ name: str,
+ metadata: "MetaData",
+ *args: SchemaItem,
+ **kw: Any,
+ ):
+ r"""Constructor for :class:`_schema.Table`.
- This method is a no-op. See the top-level
- documentation for :class:`_schema.Table`
- for constructor arguments.
- """
+ :param name: The name of this table as represented in the database.
+
+ The table name, along with the value of the ``schema`` parameter,
+ forms a key which uniquely identifies this :class:`_schema.Table`
+ within
+ the owning :class:`_schema.MetaData` collection.
+ Additional calls to :class:`_schema.Table` with the same name,
+ metadata,
+ and schema name will return the same :class:`_schema.Table` object.
+
+ Names which contain no upper case characters
+ will be treated as case insensitive names, and will not be quoted
+ unless they are a reserved word or contain special characters.
+ A name with any number of upper case characters is considered
+ to be case sensitive, and will be sent as quoted.
+
+ To enable unconditional quoting for the table name, specify the flag
+ ``quote=True`` to the constructor, or use the :class:`.quoted_name`
+ construct to specify the name.
+
+ :param metadata: a :class:`_schema.MetaData`
+ object which will contain this
+ table. The metadata is used as a point of association of this table
+ with other tables which are referenced via foreign key. It also
+ may be used to associate this table with a particular
+ :class:`.Connection` or :class:`.Engine`.
+
+ :param \*args: Additional positional arguments are used primarily
+ to add the list of :class:`_schema.Column`
+ objects contained within this
+ table. Similar to the style of a CREATE TABLE statement, other
+ :class:`.SchemaItem` constructs may be added here, including
+ :class:`.PrimaryKeyConstraint`, and
+ :class:`_schema.ForeignKeyConstraint`.
+
+ :param autoload: Defaults to ``False``, unless
+ :paramref:`_schema.Table.autoload_with`
+ is set in which case it defaults to ``True``;
+ :class:`_schema.Column` objects
+ for this table should be reflected from the database, possibly
+ augmenting objects that were explicitly specified.
+ :class:`_schema.Column` and other objects explicitly set on the
+ table will replace corresponding reflected objects.
+
+ .. deprecated:: 1.4
+
+ The autoload parameter is deprecated and will be removed in
+ version 2.0. Please use the
+ :paramref:`_schema.Table.autoload_with` parameter, passing an
+ engine or connection.
+
+ .. seealso::
+
+ :ref:`metadata_reflection_toplevel`
+
+ :param autoload_replace: Defaults to ``True``; when using
+ :paramref:`_schema.Table.autoload`
+ in conjunction with :paramref:`_schema.Table.extend_existing`,
+ indicates
+ that :class:`_schema.Column` objects present in the already-existing
+ :class:`_schema.Table`
+ object should be replaced with columns of the same
+ name retrieved from the autoload process. When ``False``, columns
+ already present under existing names will be omitted from the
+ reflection process.
+
+ Note that this setting does not impact :class:`_schema.Column` objects
+ specified programmatically within the call to :class:`_schema.Table`
+ that
+ also is autoloading; those :class:`_schema.Column` objects will always
+ replace existing columns of the same name when
+ :paramref:`_schema.Table.extend_existing` is ``True``.
+
+ .. seealso::
+
+ :paramref:`_schema.Table.autoload`
+
+ :paramref:`_schema.Table.extend_existing`
+
+ :param autoload_with: An :class:`_engine.Engine` or
+ :class:`_engine.Connection` object,
+ or a :class:`_reflection.Inspector` object as returned by
+ :func:`_sa.inspect`
+ against one, with which this :class:`_schema.Table`
+ object will be reflected.
+ When set to a non-None value, the autoload process will take place
+ for this table against the given engine or connection.
+
+ :param extend_existing: When ``True``, indicates that if this
+ :class:`_schema.Table` is already present in the given
+ :class:`_schema.MetaData`,
+ apply further arguments within the constructor to the existing
+ :class:`_schema.Table`.
+
+ If :paramref:`_schema.Table.extend_existing` or
+ :paramref:`_schema.Table.keep_existing` are not set,
+ and the given name
+ of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
+ that is
+ already present in the target :class:`_schema.MetaData` collection,
+ and
+ this :class:`_schema.Table`
+ specifies additional columns or other constructs
+ or flags that modify the table's state, an
+ error is raised. The purpose of these two mutually-exclusive flags
+ is to specify what action should be taken when a
+ :class:`_schema.Table`
+ is specified that matches an existing :class:`_schema.Table`,
+ yet specifies
+ additional constructs.
+
+ :paramref:`_schema.Table.extend_existing`
+ will also work in conjunction
+ with :paramref:`_schema.Table.autoload` to run a new reflection
+ operation against the database, even if a :class:`_schema.Table`
+ of the same name is already present in the target
+ :class:`_schema.MetaData`; newly reflected :class:`_schema.Column`
+ objects
+ and other options will be added into the state of the
+ :class:`_schema.Table`, potentially overwriting existing columns
+ and options of the same name.
+
+ As is always the case with :paramref:`_schema.Table.autoload`,
+ :class:`_schema.Column` objects can be specified in the same
+ :class:`_schema.Table`
+ constructor, which will take precedence. Below, the existing
+ table ``mytable`` will be augmented with :class:`_schema.Column`
+ objects
+ both reflected from the database, as well as the given
+ :class:`_schema.Column`
+ named "y"::
+
+ Table("mytable", metadata,
+ Column('y', Integer),
+ extend_existing=True,
+ autoload_with=engine
+ )
+
+ .. seealso::
+
+ :paramref:`_schema.Table.autoload`
+
+ :paramref:`_schema.Table.autoload_replace`
+
+ :paramref:`_schema.Table.keep_existing`
+
+
+ :param implicit_returning: True by default - indicates that
+ RETURNING can be used by default to fetch newly inserted primary key
+ values, for backends which support this. Note that
+ :func:`_sa.create_engine` also provides an ``implicit_returning``
+ flag.
+
+ :param include_columns: A list of strings indicating a subset of
+ columns to be loaded via the ``autoload`` operation; table columns who
+ aren't present in this list will not be represented on the resulting
+ ``Table`` object. Defaults to ``None`` which indicates all columns
+ should be reflected.
+
+ :param resolve_fks: Whether or not to reflect :class:`_schema.Table`
+ objects
+ related to this one via :class:`_schema.ForeignKey` objects, when
+ :paramref:`_schema.Table.autoload` or
+ :paramref:`_schema.Table.autoload_with` is
+ specified. Defaults to True. Set to False to disable reflection of
+ related tables as :class:`_schema.ForeignKey`
+ objects are encountered; may be
+ used either to save on SQL calls or to avoid issues with related tables
+ that can't be accessed. Note that if a related table is already present
+ in the :class:`_schema.MetaData` collection, or becomes present later,
+ a
+ :class:`_schema.ForeignKey` object associated with this
+ :class:`_schema.Table` will
+ resolve to that table normally.
+
+ .. versionadded:: 1.3
+
+ .. seealso::
+
+ :paramref:`.MetaData.reflect.resolve_fks`
+
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param keep_existing: When ``True``, indicates that if this Table
+ is already present in the given :class:`_schema.MetaData`, ignore
+ further arguments within the constructor to the existing
+ :class:`_schema.Table`, and return the :class:`_schema.Table`
+ object as
+ originally created. This is to allow a function that wishes
+ to define a new :class:`_schema.Table` on first call, but on
+ subsequent calls will return the same :class:`_schema.Table`,
+ without any of the declarations (particularly constraints)
+ being applied a second time.
+
+ If :paramref:`_schema.Table.extend_existing` or
+ :paramref:`_schema.Table.keep_existing` are not set,
+ and the given name
+ of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
+ that is
+ already present in the target :class:`_schema.MetaData` collection,
+ and
+ this :class:`_schema.Table`
+ specifies additional columns or other constructs
+ or flags that modify the table's state, an
+ error is raised. The purpose of these two mutually-exclusive flags
+ is to specify what action should be taken when a
+ :class:`_schema.Table`
+ is specified that matches an existing :class:`_schema.Table`,
+ yet specifies
+ additional constructs.
+
+ .. seealso::
+
+ :paramref:`_schema.Table.extend_existing`
+
+ :param listeners: A list of tuples of the form ``(<eventname>, <fn>)``
+ which will be passed to :func:`.event.listen` upon construction.
+ This alternate hook to :func:`.event.listen` allows the establishment
+ of a listener function specific to this :class:`_schema.Table` before
+ the "autoload" process begins. Historically this has been intended
+ for use with the :meth:`.DDLEvents.column_reflect` event, however
+ note that this event hook may now be associated with the
+ :class:`_schema.MetaData` object directly::
+
+ def listen_for_reflect(table, column_info):
+ "handle the column reflection event"
+ # ...
+
+ t = Table(
+ 'sometable',
+ autoload_with=engine,
+ listeners=[
+ ('column_reflect', listen_for_reflect)
+ ])
+
+ .. seealso::
+
+ :meth:`_events.DDLEvents.column_reflect`
+
+ :param must_exist: When ``True``, indicates that this Table must already
+ be present in the given :class:`_schema.MetaData` collection, else
+ an exception is raised.
+
+ :param prefixes:
+ A list of strings to insert after CREATE in the CREATE TABLE
+ statement. They will be separated by spaces.
+
+ :param quote: Force quoting of this table's name on or off, corresponding
+ to ``True`` or ``False``. When left at its default of ``None``,
+ the column identifier will be quoted according to whether the name is
+ case sensitive (identifiers with at least one upper case character are
+ treated as case sensitive), or if it's a reserved word. This flag
+ is only needed to force quoting of a reserved word which is not known
+ by the SQLAlchemy dialect.
+
+ .. note:: setting this flag to ``False`` will not provide
+ case-insensitive behavior for table reflection; table reflection
+ will always search for a mixed-case name in a case sensitive
+ fashion. Case insensitive names are specified in SQLAlchemy only
+ by stating the name with all lower case characters.
+
+ :param quote_schema: same as 'quote' but applies to the schema identifier.
+
+ :param schema: The schema name for this table, which is required if
+ the table resides in a schema other than the default selected schema
+ for the engine's database connection. Defaults to ``None``.
+
+ If the owning :class:`_schema.MetaData` of this :class:`_schema.Table`
+ specifies its
+ own :paramref:`_schema.MetaData.schema` parameter,
+ then that schema name will
+ be applied to this :class:`_schema.Table`
+ if the schema parameter here is set
+ to ``None``. To set a blank schema name on a :class:`_schema.Table`
+ that
+ would otherwise use the schema set on the owning
+ :class:`_schema.MetaData`,
+ specify the special symbol :attr:`.BLANK_SCHEMA`.
+
+ .. versionadded:: 1.0.14 Added the :attr:`.BLANK_SCHEMA` symbol to
+ allow a :class:`_schema.Table`
+ to have a blank schema name even when the
+ parent :class:`_schema.MetaData` specifies
+ :paramref:`_schema.MetaData.schema`.
+
+ The quoting rules for the schema name are the same as those for the
+ ``name`` parameter, in that quoting is applied for reserved words or
+ case-sensitive names; to enable unconditional quoting for the schema
+ name, specify the flag ``quote_schema=True`` to the constructor, or use
+ the :class:`.quoted_name` construct to specify the name.
+
+ :param comment: Optional string that will render an SQL comment on table
+ creation.
+
+ .. versionadded:: 1.2 Added the :paramref:`_schema.Table.comment`
+ parameter
+ to :class:`_schema.Table`.
+
+ :param \**kw: Additional keyword arguments not mentioned above are
+ dialect specific, and passed in the form ``<dialectname>_<argname>``.
+ See the documentation regarding an individual dialect at
+ :ref:`dialect_toplevel` for detail on documented arguments.
+
+ """ # noqa E501
+
# __init__ is overridden to prevent __new__ from
# calling the superclass constructor.
@@ -1203,7 +1220,7 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]):
) -> None:
...
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any):
r"""
Construct a new ``Column`` object.
@@ -2179,18 +2196,18 @@ class ForeignKey(DialectKWArgs, SchemaItem):
def __init__(
self,
- column,
- _constraint=None,
- use_alter=False,
- name=None,
- onupdate=None,
- ondelete=None,
- deferrable=None,
- initially=None,
- link_to_name=False,
- match=None,
- info=None,
- **dialect_kw,
+ column: Union[str, Column, SQLCoreOperations],
+ _constraint: Optional["ForeignKeyConstraint"] = None,
+ use_alter: bool = False,
+ name: Optional[str] = None,
+ onupdate: Optional[str] = None,
+ ondelete: Optional[str] = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[bool] = None,
+ link_to_name: bool = False,
+ match: Optional[str] = None,
+ info: Optional[Dict[Any, Any]] = None,
+ **dialect_kw: Any,
):
r"""
Construct a column-level FOREIGN KEY.
@@ -2337,7 +2354,7 @@ class ForeignKey(DialectKWArgs, SchemaItem):
)
return self._schema_item_copy(fk)
- def _get_colspec(self, schema=None, table_name=None):
+ def _get_colspec(self, schema=None, table_name=None, _is_copy=False):
"""Return a string based 'column specification' for this
:class:`_schema.ForeignKey`.
@@ -2357,6 +2374,14 @@ class ForeignKey(DialectKWArgs, SchemaItem):
else:
return "%s.%s" % (table_name, colname)
elif self._table_column is not None:
+ if self._table_column.table is None:
+ if _is_copy:
+ raise exc.InvalidRequestError(
+ f"Can't copy ForeignKey object which refers to "
+ f"non-table bound Column {self._table_column!r}"
+ )
+ else:
+ return self._table_column.key
return "%s.%s" % (
self._table_column.table.fullname,
self._table_column.key,
@@ -3858,6 +3883,7 @@ class ForeignKeyConstraint(ColumnCollectionConstraint):
if target_table is not None
and x._table_key() == x.parent.table.key
else None,
+ _is_copy=True,
)
for x in self.elements
],
@@ -4331,10 +4357,10 @@ class MetaData(SchemaItem):
def __init__(
self,
- schema=None,
- quote_schema=None,
- naming_convention=None,
- info=None,
+ schema: Optional[str] = None,
+ quote_schema: Optional[bool] = None,
+ naming_convention: Optional[Dict[str, str]] = None,
+ info: Optional[Dict[Any, Any]] = None,
):
"""Create a new MetaData object.
@@ -4465,7 +4491,7 @@ class MetaData(SchemaItem):
self._sequences = {}
self._fk_memos = collections.defaultdict(list)
- tables = None
+ tables: Dict[str, Table]
"""A dictionary of :class:`_schema.Table`
objects keyed to their name or "table key".
@@ -4483,10 +4509,10 @@ class MetaData(SchemaItem):
"""
- def __repr__(self):
+ def __repr__(self) -> str:
return "MetaData()"
- def __contains__(self, table_or_key):
+ def __contains__(self, table_or_key: Union[str, Table]) -> bool:
if not isinstance(table_or_key, str):
table_or_key = table_or_key.key
return table_or_key in self.tables
@@ -4530,20 +4556,20 @@ class MetaData(SchemaItem):
self._schemas = state["schemas"]
self._fk_memos = state["fk_memos"]
- def clear(self):
+ def clear(self) -> None:
"""Clear all Table objects from this MetaData."""
dict.clear(self.tables)
self._schemas.clear()
self._fk_memos.clear()
- def remove(self, table):
+ def remove(self, table: Table) -> None:
"""Remove the given Table object from this MetaData."""
self._remove_table(table.name, table.schema)
@property
- def sorted_tables(self):
+ def sorted_tables(self) -> List[Table]:
"""Returns a list of :class:`_schema.Table` objects sorted in order of
foreign key dependency.
@@ -4599,14 +4625,14 @@ class MetaData(SchemaItem):
def reflect(
self,
- bind,
- schema=None,
- views=False,
- only=None,
- extend_existing=False,
- autoload_replace=True,
- resolve_fks=True,
- **dialect_kwargs,
+ bind: Union["Engine", "Connection"],
+ schema: Optional[str] = None,
+ views: bool = False,
+ only: Optional[_typing_Sequence[str]] = None,
+ extend_existing: bool = False,
+ autoload_replace: bool = True,
+ resolve_fks: bool = True,
+ **dialect_kwargs: Any,
):
r"""Load all available table definitions from the database.
@@ -4754,7 +4780,12 @@ class MetaData(SchemaItem):
except exc.UnreflectableTableError as uerr:
util.warn("Skipping table %s: %s" % (name, uerr))
- def create_all(self, bind, tables=None, checkfirst=True):
+ def create_all(
+ self,
+ bind: Union["Engine", "Connection"],
+ tables: Optional[_typing_Sequence[Table]] = None,
+ checkfirst: bool = True,
+ ):
"""Create all tables stored in this metadata.
Conditional by default, will not attempt to recreate tables already
@@ -4777,7 +4808,12 @@ class MetaData(SchemaItem):
ddl.SchemaGenerator, self, checkfirst=checkfirst, tables=tables
)
- def drop_all(self, bind, tables=None, checkfirst=True):
+ def drop_all(
+ self,
+ bind: Union["Engine", "Connection"],
+ tables: Optional[_typing_Sequence[Table]] = None,
+ checkfirst: bool = True,
+ ):
"""Drop all tables stored in this metadata.
Conditional by default, will not attempt to drop tables not present in
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index e1bbcffec..b0985f75d 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -12,14 +12,13 @@ SQL tables and derived rowsets.
"""
import collections
+from enum import Enum
import itertools
from operator import attrgetter
import typing
from typing import Any as TODO_Any
from typing import Optional
from typing import Tuple
-from typing import Type
-from typing import Union
from . import cache_key
from . import coercions
@@ -28,6 +27,7 @@ from . import roles
from . import traversals
from . import type_api
from . import visitors
+from ._typing import _ColumnsClauseElement
from .annotation import Annotated
from .annotation import SupportsCloneAnnotations
from .base import _clone
@@ -847,8 +847,11 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
return self.alias(name=name)
-LABEL_STYLE_NONE = util.symbol(
- "LABEL_STYLE_NONE",
+class SelectLabelStyle(Enum):
+ """Label style constants that may be passed to
+ :meth:`_sql.Select.set_label_style`."""
+
+ LABEL_STYLE_NONE = 0
"""Label style indicating no automatic labeling should be applied to the
columns clause of a SELECT statement.
@@ -867,11 +870,9 @@ LABEL_STYLE_NONE = util.symbol(
.. versionadded:: 1.4
-""", # noqa E501
-)
+ """ # noqa E501
-LABEL_STYLE_TABLENAME_PLUS_COL = util.symbol(
- "LABEL_STYLE_TABLENAME_PLUS_COL",
+ LABEL_STYLE_TABLENAME_PLUS_COL = 1
"""Label style indicating all columns should be labeled as
``<tablename>_<columnname>`` when generating the columns clause of a SELECT
statement, to disambiguate same-named columns referenced from different
@@ -897,12 +898,9 @@ LABEL_STYLE_TABLENAME_PLUS_COL = util.symbol(
.. versionadded:: 1.4
-""", # noqa E501
-)
+ """ # noqa: E501
-
-LABEL_STYLE_DISAMBIGUATE_ONLY = util.symbol(
- "LABEL_STYLE_DISAMBIGUATE_ONLY",
+ LABEL_STYLE_DISAMBIGUATE_ONLY = 2
"""Label style indicating that columns with a name that conflicts with
an existing name should be labeled with a semi-anonymizing label
when generating the columns clause of a SELECT statement.
@@ -924,17 +922,24 @@ LABEL_STYLE_DISAMBIGUATE_ONLY = util.symbol(
.. versionadded:: 1.4
-""", # noqa: E501,
-)
+ """ # noqa: E501
+ LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
+ """The default label style, refers to
+ :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`.
-LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
-"""The default label style, refers to
-:data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`.
+ .. versionadded:: 1.4
-.. versionadded:: 1.4
+ """
-"""
+
+(
+ LABEL_STYLE_NONE,
+ LABEL_STYLE_TABLENAME_PLUS_COL,
+ LABEL_STYLE_DISAMBIGUATE_ONLY,
+) = list(SelectLabelStyle)
+
+LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
class Join(roles.DMLTableRole, FromClause):
@@ -2870,10 +2875,12 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
else:
return self
- def get_label_style(self):
+ def get_label_style(self) -> SelectLabelStyle:
return self._label_style
- def set_label_style(self, label_style):
+ def set_label_style(
+ self, label_style: SelectLabelStyle
+ ) -> "SelectStatementGrouping":
return SelectStatementGrouping(
self.element.set_label_style(label_style)
)
@@ -3018,7 +3025,7 @@ class GenerativeSelect(SelectBase):
)
return self
- def get_label_style(self):
+ def get_label_style(self) -> SelectLabelStyle:
"""
Retrieve the current label style.
@@ -3027,14 +3034,16 @@ class GenerativeSelect(SelectBase):
"""
return self._label_style
- def set_label_style(self, style):
+ def set_label_style(
+ self: SelfGenerativeSelect, style: SelectLabelStyle
+ ) -> SelfGenerativeSelect:
"""Return a new selectable with the specified label style.
There are three "label styles" available,
- :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`,
- :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`, and
- :data:`_sql.LABEL_STYLE_NONE`. The default style is
- :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`.
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY`,
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`, and
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_NONE`. The default style is
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`.
In modern SQLAlchemy, there is not generally a need to change the
labeling style, as per-expression labels are more effectively used by
@@ -4131,7 +4140,7 @@ class Select(
stmt.__dict__.update(kw)
return stmt
- def __init__(self, *entities: Union[roles.ColumnsClauseRole, Type]):
+ def __init__(self, *entities: _ColumnsClauseElement):
r"""Construct a new :class:`_expression.Select`.
The public constructor for :class:`_expression.Select` is the
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index dd29b2c3a..6b878dc70 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -13,6 +13,7 @@ import typing
from typing import Any
from typing import Callable
from typing import Generic
+from typing import Optional
from typing import Tuple
from typing import Type
from typing import TypeVar
@@ -21,7 +22,7 @@ from typing import Union
from .base import SchemaEventTarget
from .cache_key import NO_CACHE
from .operators import ColumnOperators
-from .visitors import Traversible
+from .visitors import Visitable
from .. import exc
from .. import util
@@ -52,7 +53,7 @@ _CT = TypeVar("_CT", bound=Any)
SelfTypeEngine = typing.TypeVar("SelfTypeEngine", bound="TypeEngine")
-class TypeEngine(Traversible, Generic[_T]):
+class TypeEngine(Visitable, Generic[_T]):
"""The ultimate base class for all SQL datatypes.
Common subclasses of :class:`.TypeEngine` include
@@ -573,7 +574,7 @@ class TypeEngine(Traversible, Generic[_T]):
raise NotImplementedError()
def with_variant(
- self: SelfTypeEngine, type_: "TypeEngine", dialect_name: str
+ self: SelfTypeEngine, type_: "TypeEngine", *dialect_names: str
) -> SelfTypeEngine:
r"""Produce a copy of this type object that will utilize the given
type when applied to the dialect of the given name.
@@ -586,7 +587,7 @@ class TypeEngine(Traversible, Generic[_T]):
string_type = String()
string_type = string_type.with_variant(
- mysql.VARCHAR(collation='foo'), 'mysql'
+ mysql.VARCHAR(collation='foo'), 'mysql', 'mariadb'
)
The variant mapping indicates that when this type is
@@ -602,16 +603,20 @@ class TypeEngine(Traversible, Generic[_T]):
:param type\_: a :class:`.TypeEngine` that will be selected
as a variant from the originating type, when a dialect
of the given name is in use.
- :param dialect_name: base name of the dialect which uses
- this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.)
+ :param \*dialect_names: one or more base names of the dialect which
+ uses this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.)
+
+ .. versionchanged:: 2.0 multiple dialect names can be specified
+ for one variant.
"""
- if dialect_name in self._variant_mapping:
- raise exc.ArgumentError(
- "Dialect '%s' is already present in "
- "the mapping for this %r" % (dialect_name, self)
- )
+ for dialect_name in dialect_names:
+ if dialect_name in self._variant_mapping:
+ raise exc.ArgumentError(
+ "Dialect '%s' is already present in "
+ "the mapping for this %r" % (dialect_name, self)
+ )
new_type = self.copy()
if isinstance(type_, type):
type_ = type_()
@@ -620,8 +625,9 @@ class TypeEngine(Traversible, Generic[_T]):
"can't pass a type that already has variants as a "
"dialect-level type to with_variant()"
)
+
new_type._variant_mapping = self._variant_mapping.union(
- {dialect_name: type_}
+ {dialect_name: type_ for dialect_name in dialect_names}
)
return new_type
@@ -919,7 +925,7 @@ class ExternalType:
"""
- cache_ok = None
+ cache_ok: Optional[bool] = None
"""Indicate if statements using this :class:`.ExternalType` are "safe to
cache".
@@ -1357,6 +1363,8 @@ class TypeDecorator(ExternalType, SchemaEventTarget, TypeEngine[_T]):
_is_type_decorator = True
+ impl: Union[TypeEngine[Any], Type[TypeEngine[Any]]]
+
def __init__(self, *args, **kwargs):
"""Construct a :class:`.TypeDecorator`.