diff options
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r-- | lib/sqlalchemy/sql/__init__.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/_selectable_constructors.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/_typing.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/base.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/ddl.py | 20 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/naming.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/roles.py | 29 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/schema.py | 744 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 67 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/type_api.py | 34 |
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`. |