diff -Nru alembic-1.7.1/alembic/autogenerate/api.py alembic-1.7.6/alembic/autogenerate/api.py --- alembic-1.7.1/alembic/autogenerate/api.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/autogenerate/api.py 2022-02-01 15:00:09.000000000 +0000 @@ -341,6 +341,11 @@ @util.memoized_property def inspector(self) -> "Inspector": + if self.connection is None: + raise TypeError( + "can't return inspector as this " + "AutogenContext has no database connection" + ) return inspect(self.connection) @contextlib.contextmanager diff -Nru alembic-1.7.1/alembic/autogenerate/render.py alembic-1.7.6/alembic/autogenerate/render.py --- alembic-1.7.1/alembic/autogenerate/render.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/autogenerate/render.py 2022-02-01 15:00:09.000000000 +0000 @@ -18,9 +18,7 @@ from .. import util from ..operations import ops -from ..util import compat from ..util import sqla_compat -from ..util.compat import string_types if TYPE_CHECKING: from typing import Literal @@ -41,7 +39,6 @@ from sqlalchemy.sql.schema import UniqueConstraint from sqlalchemy.sql.sqltypes import ARRAY from sqlalchemy.sql.type_api import TypeEngine - from sqlalchemy.sql.type_api import Variant from alembic.autogenerate.api import AutogenContext from alembic.config import Config @@ -568,8 +565,8 @@ if name is None: return name elif isinstance(name, sql.elements.quoted_name): - return compat.text_type(name) - elif isinstance(name, compat.string_types): + return str(name) + elif isinstance(name, str): return name @@ -758,14 +755,14 @@ elif sqla_compat._server_default_is_identity(default): return _render_identity(cast("Identity", default), autogen_context) elif isinstance(default, sa_schema.DefaultClause): - if isinstance(default.arg, compat.string_types): + if isinstance(default.arg, str): default = default.arg else: return _render_potential_expr( default.arg, autogen_context, is_server_default=True ) - if isinstance(default, string_types) and repr_: + if isinstance(default, str) and repr_: default = repr(re.sub(r"^'|'$", "", default)) return cast(str, default) @@ -812,7 +809,11 @@ return kwargs -def _repr_type(type_: "TypeEngine", autogen_context: "AutogenContext") -> str: +def _repr_type( + type_: "TypeEngine", + autogen_context: "AutogenContext", + _skip_variants: bool = False, +) -> str: rendered = _user_defined_render("type", type_, autogen_context) if rendered is not False: return rendered @@ -839,7 +840,7 @@ elif impl_rt: return impl_rt elif mod.startswith("sqlalchemy."): - if type(type_) is sqltypes.Variant: + if not _skip_variants and sqla_compat._type_has_variants(type_): return _render_Variant_type(type_, autogen_context) if "_render_%s_type" % type_.__visit_name__ in globals(): fn = globals()["_render_%s_type" % type_.__visit_name__] @@ -864,14 +865,15 @@ def _render_Variant_type( - type_: "Variant", autogen_context: "AutogenContext" + type_: "TypeEngine", autogen_context: "AutogenContext" ) -> str: - base = _repr_type(type_.impl, autogen_context) + base_type, variant_mapping = sqla_compat._get_variant_mapping(type_) + base = _repr_type(base_type, autogen_context, _skip_variants=True) assert base is not None and base is not False - for dialect in sorted(type_.mapping): - typ = type_.mapping[dialect] + for dialect in sorted(variant_mapping): + typ = variant_mapping[dialect] base += ".with_variant(%s, %r)" % ( - _repr_type(typ, autogen_context), + _repr_type(typ, autogen_context, _skip_variants=True), dialect, ) return base @@ -1105,7 +1107,7 @@ def _execute_sql( autogen_context: "AutogenContext", op: "ops.ExecuteSQLOp" ) -> str: - if not isinstance(op.sqltext, string_types): + if not isinstance(op.sqltext, str): raise NotImplementedError( "Autogenerate rendering of SQL Expression language constructs " "not supported here; please use a plain SQL string" diff -Nru alembic-1.7.1/alembic/command.py alembic-1.7.6/alembic/command.py --- alembic-1.7.1/alembic/command.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/command.py 2022-02-01 15:00:09.000000000 +0000 @@ -643,3 +643,29 @@ ) for sc in revs: util.open_in_editor(sc.path) + + +def ensure_version(config: "Config", sql: bool = False) -> None: + """Create the alembic version table if it doesn't exist already . + + :param config: a :class:`.Config` instance. + + :param sql: use ``--sql`` mode + + .. versionadded:: 1.7.6 + + """ + + script = ScriptDirectory.from_config(config) + + def do_ensure_version(rev, context): + context._ensure_version_table() + return [] + + with EnvironmentContext( + config, + script, + fn=do_ensure_version, + as_sql=sql, + ): + script.run_env() diff -Nru alembic-1.7.1/alembic/config.py alembic-1.7.6/alembic/config.py --- alembic-1.7.1/alembic/config.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/config.py 2022-02-01 15:00:09.000000000 +0000 @@ -167,9 +167,9 @@ """ if arg: - output = compat.text_type(text) % arg + output = str(text) % arg else: - output = compat.text_type(text) + output = str(text) util.write_outstream(self.stdout, output, "\n") diff -Nru alembic-1.7.1/alembic/context.pyi alembic-1.7.6/alembic/context.pyi --- alembic-1.7.1/alembic/context.pyi 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/context.pyi 2022-02-01 15:00:09.000000000 +0000 @@ -13,8 +13,10 @@ from sqlalchemy.engine.base import Connection from sqlalchemy.sql.schema import MetaData - from .migration import MigrationContext + from .config import Config from .runtime.migration import _ProxyTransaction + from .runtime.migration import MigrationContext + from .script import ScriptDirectory ### end imports ### @@ -62,6 +64,8 @@ """ +config: Config + def configure( connection: Optional["Connection"] = None, url: Optional[str] = None, @@ -715,6 +719,8 @@ """ +script: ScriptDirectory + def static_output(text): """Emit text directly to the "offline" SQL stream. diff -Nru alembic-1.7.1/alembic/ddl/impl.py alembic-1.7.6/alembic/ddl/impl.py --- alembic-1.7.1/alembic/ddl/impl.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/ddl/impl.py 2022-02-01 15:00:09.000000000 +0000 @@ -19,8 +19,6 @@ from . import base from .. import util from ..util import sqla_compat -from ..util.compat import string_types -from ..util.compat import text_type if TYPE_CHECKING: from io import StringIO @@ -124,7 +122,7 @@ def static_output(self, text: str) -> None: assert self.output_buffer is not None - self.output_buffer.write(text_type(text + "\n\n")) + self.output_buffer.write(text + "\n\n") self.output_buffer.flush() def requires_recreate_in_batch( @@ -162,7 +160,7 @@ multiparams: Sequence[dict] = (), params: Dict[str, int] = util.immutabledict(), ) -> Optional[Union["LegacyCursorResult", "CursorResult"]]: - if isinstance(construct, string_types): + if isinstance(construct, str): construct = text(construct) if self.as_sql: if multiparams or params: @@ -177,9 +175,7 @@ compile_kw = {} self.static_output( - text_type( - construct.compile(dialect=self.dialect, **compile_kw) - ) + str(construct.compile(dialect=self.dialect, **compile_kw)) .replace("\t", " ") .strip() + self.command_terminator @@ -554,8 +550,8 @@ def correct_for_autogen_constraints( self, - conn_uniques: Union[Set["UniqueConstraint"]], - conn_indexes: Union[Set["Index"]], + conn_uniques: Set["UniqueConstraint"], + conn_indexes: Set["Index"], metadata_unique_constraints: Set["UniqueConstraint"], metadata_indexes: Set["Index"], ) -> None: @@ -580,7 +576,7 @@ compile_kw = dict( compile_kwargs={"literal_binds": True, "include_table": False} ) - return text_type(expr.compile(dialect=self.dialect, **compile_kw)) + return str(expr.compile(dialect=self.dialect, **compile_kw)) def _compat_autogen_column_reflect( self, inspector: "Inspector" diff -Nru alembic-1.7.1/alembic/ddl/mssql.py alembic-1.7.6/alembic/ddl/mssql.py --- alembic-1.7.1/alembic/ddl/mssql.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/ddl/mssql.py 2022-02-01 15:00:09.000000000 +0000 @@ -255,6 +255,8 @@ class _ExecDropConstraint(Executable, ClauseElement): + inherit_cache = False + def __init__( self, tname: str, @@ -269,6 +271,8 @@ class _ExecDropFKConstraint(Executable, ClauseElement): + inherit_cache = False + def __init__( self, tname: str, colname: "Column", schema: Optional[str] ) -> None: diff -Nru alembic-1.7.1/alembic/ddl/mysql.py alembic-1.7.6/alembic/ddl/mysql.py --- alembic-1.7.1/alembic/ddl/mysql.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/ddl/mysql.py 2022-02-01 15:00:09.000000000 +0000 @@ -38,7 +38,10 @@ __dialect__ = "mysql" transactional_ddl = False - type_synonyms = DefaultImpl.type_synonyms + ({"BOOL", "TINYINT"},) + type_synonyms = DefaultImpl.type_synonyms + ( + {"BOOL", "TINYINT"}, + {"JSON", "LONGTEXT"}, + ) type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"] def alter_column( # type:ignore[override] diff -Nru alembic-1.7.1/alembic/ddl/postgresql.py alembic-1.7.6/alembic/ddl/postgresql.py --- alembic-1.7.1/alembic/ddl/postgresql.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/ddl/postgresql.py 2022-02-01 15:00:09.000000000 +0000 @@ -38,7 +38,6 @@ from ..operations import schemaobj from ..operations.base import BatchOperations from ..operations.base import Operations -from ..util import compat from ..util import sqla_compat if TYPE_CHECKING: @@ -118,9 +117,7 @@ if ( not isinstance(inspector_column.type, Numeric) and metadata_column.server_default is not None - and isinstance( - metadata_column.server_default.arg, compat.string_types - ) + and isinstance(metadata_column.server_default.arg, str) and not re.match(r"^'.*'$", rendered_metadata_default) ): rendered_metadata_default = "'%s'" % rendered_metadata_default diff -Nru alembic-1.7.1/alembic/ddl/sqlite.py alembic-1.7.6/alembic/ddl/sqlite.py --- alembic-1.7.1/alembic/ddl/sqlite.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/ddl/sqlite.py 2022-02-01 15:00:09.000000000 +0000 @@ -66,14 +66,14 @@ # auto-gen constraint and an explicit one if const._create_rule is None: # type:ignore[attr-defined] raise NotImplementedError( - "No support for ALTER of constraints in SQLite dialect" + "No support for ALTER of constraints in SQLite dialect. " "Please refer to the batch mode feature which allows for " "SQLite migrations using a copy-and-move strategy." ) elif const._create_rule(self): # type:ignore[attr-defined] util.warn( "Skipping unsupported ALTER for " - "creation of implicit constraint" + "creation of implicit constraint. " "Please refer to the batch mode feature which allows for " "SQLite migrations using a copy-and-move strategy." ) @@ -81,7 +81,7 @@ def drop_constraint(self, const: "Constraint"): if const._create_rule is None: # type:ignore[attr-defined] raise NotImplementedError( - "No support for ALTER of constraints in SQLite dialect" + "No support for ALTER of constraints in SQLite dialect. " "Please refer to the batch mode feature which allows for " "SQLite migrations using a copy-and-move strategy." ) diff -Nru alembic-1.7.1/alembic/__init__.py alembic-1.7.6/alembic/__init__.py --- alembic-1.7.1/alembic/__init__.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/__init__.py 2022-02-01 15:00:09.000000000 +0000 @@ -3,4 +3,4 @@ from . import context from . import op -__version__ = "1.7.1" +__version__ = "1.7.6" diff -Nru alembic-1.7.1/alembic/operations/base.py alembic-1.7.6/alembic/operations/base.py --- alembic-1.7.1/alembic/operations/base.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/operations/base.py 2022-02-01 15:00:09.000000000 +0000 @@ -17,9 +17,11 @@ from . import schemaobj from .. import util from ..util import sqla_compat +from ..util.compat import formatannotation_fwdref from ..util.compat import inspect_formatargspec from ..util.compat import inspect_getfullargspec + NoneType = type(None) if TYPE_CHECKING: @@ -121,7 +123,9 @@ name_args[0:2] = ["self"] - args = inspect_formatargspec(*spec) + args = inspect_formatargspec( + *spec, formatannotation=formatannotation_fwdref + ) num_defaults = len(spec[3]) if spec[3] else 0 if num_defaults: defaulted_vals = name_args[0 - num_defaults :] @@ -134,6 +138,7 @@ spec[2], defaulted_vals, formatvalue=lambda x: "=" + x, + formatannotation=formatannotation_fwdref, ) args = re.sub( diff -Nru alembic-1.7.1/alembic/operations/batch.py alembic-1.7.6/alembic/operations/batch.py --- alembic-1.7.1/alembic/operations/batch.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/operations/batch.py 2022-02-01 15:00:09.000000000 +0000 @@ -28,6 +28,7 @@ from ..util.sqla_compat import _insert_inline from ..util.sqla_compat import _is_type_bound from ..util.sqla_compat import _remove_column_from_collection +from ..util.sqla_compat import _resolve_for_variant from ..util.sqla_compat import _select if TYPE_CHECKING: @@ -462,16 +463,22 @@ existing.name = name existing_transfer["name"] = name - # pop named constraints for Boolean/Enum for rename - if ( - "existing_type" in kw - and isinstance(kw["existing_type"], SchemaEventTarget) - and kw["existing_type"].name # type:ignore[attr-defined] - ): - self.named_constraints.pop( - kw["existing_type"].name, None # type:ignore[attr-defined] + existing_type = kw.get("existing_type", None) + if existing_type: + resolved_existing_type = _resolve_for_variant( + kw["existing_type"], self.impl.dialect ) + # pop named constraints for Boolean/Enum for rename + if ( + isinstance(resolved_existing_type, SchemaEventTarget) + and resolved_existing_type.name # type:ignore[attr-defined] # noqa E501 + ): + self.named_constraints.pop( + resolved_existing_type.name, + None, # type:ignore[attr-defined] + ) + if type_ is not None: type_ = sqltypes.to_instance(type_) # old type is being discarded so turn off eventing diff -Nru alembic-1.7.1/alembic/operations/ops.py alembic-1.7.6/alembic/operations/ops.py --- alembic-1.7.1/alembic/operations/ops.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/operations/ops.py 2022-02-01 15:00:09.000000000 +0000 @@ -296,7 +296,7 @@ def create_primary_key( cls, operations: "Operations", - constraint_name: str, + constraint_name: Optional[str], table_name: str, columns: List[str], schema: Optional[str] = None, @@ -321,9 +321,9 @@ off normally. The :class:`~sqlalchemy.schema.AddConstraint` construct is ultimately used to generate the ALTER statement. - :param name: Name of the primary key constraint. The name is necessary - so that an ALTER statement can be emitted. For setups that - use an automated naming scheme such as that described at + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at :ref:`sqla:constraint_naming_conventions` ``name`` here can be ``None``, as the event listener will apply the name to the constraint object when it is associated @@ -588,7 +588,7 @@ def create_foreign_key( cls, operations: "Operations", - constraint_name: str, + constraint_name: Optional[str], source_table: str, referent_table: str, local_cols: List[str], @@ -621,9 +621,9 @@ off normally. The :class:`~sqlalchemy.schema.AddConstraint` construct is ultimately used to generate the ALTER statement. - :param name: Name of the foreign key constraint. The name is necessary - so that an ALTER statement can be emitted. For setups that - use an automated naming scheme such as that described at + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at :ref:`sqla:constraint_naming_conventions`, ``name`` here can be ``None``, as the event listener will apply the name to the constraint object when it is associated @@ -732,7 +732,7 @@ self, constraint_name: Optional[str], table_name: str, - condition: Union["TextClause", "ColumnElement[Any]"], + condition: Union[str, "TextClause", "ColumnElement[Any]"], schema: Optional[str] = None, **kw ) -> None: @@ -778,7 +778,7 @@ operations: "Operations", constraint_name: Optional[str], table_name: str, - condition: "BinaryExpression", + condition: Union[str, "BinaryExpression"], schema: Optional[str] = None, **kw ) -> Optional["Table"]: @@ -2389,7 +2389,7 @@ op.execute("INSERT INTO table (foo) VALUES ('\:colon_value')") - :param sql: Any legal SQLAlchemy expression, including: + :param sqltext: Any legal SQLAlchemy expression, including: * a string * a :func:`sqlalchemy.sql.expression.text` construct. diff -Nru alembic-1.7.1/alembic/operations/schemaobj.py alembic-1.7.6/alembic/operations/schemaobj.py --- alembic-1.7.1/alembic/operations/schemaobj.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/operations/schemaobj.py 2022-02-01 15:00:09.000000000 +0000 @@ -16,7 +16,6 @@ from .. import util from ..util import sqla_compat -from ..util.compat import string_types if TYPE_CHECKING: from sqlalchemy.sql.elements import ColumnElement @@ -137,7 +136,7 @@ self, name: Optional[str], source: str, - condition: Union["TextClause", "ColumnElement[Any]"], + condition: Union[str, "TextClause", "ColumnElement[Any]"], schema: Optional[str] = None, **kw ) -> Union["CheckConstraint"]: @@ -269,7 +268,7 @@ ForeignKey. """ - if isinstance(fk._colspec, string_types): # type:ignore[attr-defined] + if isinstance(fk._colspec, str): # type:ignore[attr-defined] table_key, cname = fk._colspec.rsplit( # type:ignore[attr-defined] ".", 1 ) diff -Nru alembic-1.7.1/alembic/op.pyi alembic-1.7.6/alembic/op.pyi --- alembic-1.7.1/alembic/op.pyi 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/op.pyi 2022-02-01 15:00:09.000000000 +0000 @@ -422,7 +422,7 @@ def create_check_constraint( constraint_name: Optional[str], table_name: str, - condition: "BinaryExpression", + condition: Union[str, "BinaryExpression"], schema: Optional[str] = None, **kw ) -> Optional["Table"]: @@ -508,7 +508,7 @@ """ def create_foreign_key( - constraint_name: str, + constraint_name: Optional[str], source_table: str, referent_table: str, local_cols: List[str], @@ -541,9 +541,9 @@ off normally. The :class:`~sqlalchemy.schema.AddConstraint` construct is ultimately used to generate the ALTER statement. - :param name: Name of the foreign key constraint. The name is necessary - so that an ALTER statement can be emitted. For setups that - use an automated naming scheme such as that described at + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at :ref:`sqla:constraint_naming_conventions`, ``name`` here can be ``None``, as the event listener will apply the name to the constraint object when it is associated @@ -618,7 +618,7 @@ """ def create_primary_key( - constraint_name: str, + constraint_name: Optional[str], table_name: str, columns: List[str], schema: Optional[str] = None, @@ -643,9 +643,9 @@ off normally. The :class:`~sqlalchemy.schema.AddConstraint` construct is ultimately used to generate the ALTER statement. - :param name: Name of the primary key constraint. The name is necessary - so that an ALTER statement can be emitted. For setups that - use an automated naming scheme such as that described at + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at :ref:`sqla:constraint_naming_conventions` ``name`` here can be ``None``, as the event listener will apply the name to the constraint object when it is associated @@ -1002,7 +1002,7 @@ op.execute("INSERT INTO table (foo) VALUES ('\:colon_value')") - :param sql: Any legal SQLAlchemy expression, including: + :param sqltext: Any legal SQLAlchemy expression, including: * a string * a :func:`sqlalchemy.sql.expression.text` construct. diff -Nru alembic-1.7.1/alembic/runtime/migration.py alembic-1.7.6/alembic/runtime/migration.py --- alembic-1.7.1/alembic/runtime/migration.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/runtime/migration.py 2022-02-01 15:00:09.000000000 +0000 @@ -335,11 +335,19 @@ self.connection = ( self.impl.connection ) = base_connection.execution_options(isolation_level="AUTOCOMMIT") + + # sqlalchemy future mode will "autobegin" in any case, so take + # control of that "transaction" here + fake_trans: Optional[Transaction] = self.connection.begin() + else: + fake_trans = None try: yield finally: if not self.as_sql: assert self.connection is not None + if fake_trans is not None: + fake_trans.commit() self.connection.execution_options( isolation_level=current_level ) diff -Nru alembic-1.7.1/alembic/script/revision.py alembic-1.7.6/alembic/script/revision.py --- alembic-1.7.1/alembic/script/revision.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/script/revision.py 2022-02-01 15:00:09.000000000 +0000 @@ -7,6 +7,7 @@ from typing import Deque from typing import Dict from typing import FrozenSet +from typing import Iterable from typing import Iterator from typing import List from typing import Optional @@ -20,7 +21,6 @@ from sqlalchemy import util as sqlautil from .. import util -from ..util import compat if TYPE_CHECKING: from typing import Literal @@ -112,7 +112,7 @@ """ - def __init__(self, generator: Callable[[], Iterator["Revision"]]) -> None: + def __init__(self, generator: Callable[[], Iterable["Revision"]]) -> None: """Construct a new :class:`.RevisionMap`. :param generator: a zero-arg callable that will generate an iterable @@ -666,7 +666,7 @@ def filter_for_lineage( self, - targets: Sequence[_T], + targets: Iterable[_T], check_against: Optional[str], include_dependencies: bool = False, ) -> Tuple[_T, ...]: @@ -724,16 +724,12 @@ self, id_: Optional[str] ) -> Tuple[Tuple[str, ...], Optional[str]]: branch_label: Optional[str] - if isinstance(id_, compat.string_types) and "@" in id_: + if isinstance(id_, str) and "@" in id_: branch_label, id_ = id_.split("@", 1) elif id_ is not None and ( - ( - isinstance(id_, tuple) - and id_ - and not isinstance(id_[0], compat.string_types) - ) - or not isinstance(id_, compat.string_types + (tuple,)) + (isinstance(id_, tuple) and id_ and not isinstance(id_[0], str)) + or not isinstance(id_, (str, tuple)) ): raise RevisionError( "revision identifier %r is not a string; ensure database " @@ -1029,7 +1025,7 @@ walk to. """ initial: Optional[_RevisionOrBase] - if isinstance(start, compat.string_types): + if isinstance(start, str): initial = self.get_revision(start) else: initial = start @@ -1092,7 +1088,7 @@ if target is None: return None, None assert isinstance( - target, compat.string_types + target, str ), "Expected downgrade target in string form" match = _relative_destination.match(target) if match: @@ -1183,7 +1179,7 @@ to. The target may be specified in absolute form, or relative to :current_revisions. """ - if isinstance(target, compat.string_types): + if isinstance(target, str): match = _relative_destination.match(target) else: match = None @@ -1400,7 +1396,7 @@ # Handled named bases (e.g. branch@... -> heads should only produce # targets on the given branch) - if isinstance(lower, compat.string_types) and "@" in lower: + if isinstance(lower, str) and "@" in lower: branch, _, _ = lower.partition("@") branch_rev = self.get_revision(branch) if branch_rev is not None and branch_rev.revision == branch: diff -Nru alembic-1.7.1/alembic/script/write_hooks.py alembic-1.7.6/alembic/script/write_hooks.py --- alembic-1.7.1/alembic/script/write_hooks.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/script/write_hooks.py 2022-02-01 15:00:09.000000000 +0000 @@ -5,6 +5,7 @@ from typing import Callable from typing import Dict from typing import List +from typing import Optional from typing import Union from .. import util @@ -113,7 +114,9 @@ @register("console_scripts") -def console_scripts(path, options, ignore_output=False): +def console_scripts( + path: str, options: dict, ignore_output: bool = False +) -> None: try: entrypoint_name = options["entrypoint"] @@ -124,17 +127,17 @@ ) from ke for entry in compat.importlib_metadata_get("console_scripts"): if entry.name == entrypoint_name: - impl = entry + impl: Any = entry break else: raise util.CommandError( f"Could not find entrypoint console_scripts.{entrypoint_name}" ) - cwd = options.get("cwd", None) + cwd: Optional[str] = options.get("cwd", None) cmdline_options_str = options.get("options", "") cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path) - kw = {} + kw: Dict[str, Any] = {} if ignore_output: kw["stdout"] = kw["stderr"] = subprocess.DEVNULL diff -Nru alembic-1.7.1/alembic/templates/async/alembic.ini.mako alembic-1.7.6/alembic/templates/async/alembic.ini.mako --- alembic-1.7.1/alembic/templates/async/alembic.ini.mako 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/async/alembic.ini.mako 2022-02-01 15:00:09.000000000 +0000 @@ -35,16 +35,18 @@ # version location specification; This defaults # to ${script_location}/versions. When using multiple version # directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" +# The path separator used here should be the separator specified by "version_path_separator" below. # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions # version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space -version_path_separator = os # default: use os.pathsep +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. # the output encoding used when revision files # are written from script.py.mako diff -Nru alembic-1.7.1/alembic/templates/async/env.py alembic-1.7.6/alembic/templates/async/env.py --- alembic-1.7.1/alembic/templates/async/env.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/async/env.py 2022-02-01 15:00:09.000000000 +0000 @@ -77,6 +77,8 @@ async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) + await connectable.dispose() + if context.is_offline_mode(): run_migrations_offline() diff -Nru alembic-1.7.1/alembic/templates/generic/alembic.ini.mako alembic-1.7.6/alembic/templates/generic/alembic.ini.mako --- alembic-1.7.1/alembic/templates/generic/alembic.ini.mako 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/generic/alembic.ini.mako 2022-02-01 15:00:09.000000000 +0000 @@ -35,16 +35,18 @@ # version location specification; This defaults # to ${script_location}/versions. When using multiple version # directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" +# The path separator used here should be the separator specified by "version_path_separator" below. # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions # version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space -version_path_separator = os # default: use os.pathsep +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. # the output encoding used when revision files # are written from script.py.mako diff -Nru alembic-1.7.1/alembic/templates/multidb/alembic.ini.mako alembic-1.7.6/alembic/templates/multidb/alembic.ini.mako --- alembic-1.7.1/alembic/templates/multidb/alembic.ini.mako 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/multidb/alembic.ini.mako 2022-02-01 15:00:09.000000000 +0000 @@ -35,16 +35,18 @@ # version location specification; This defaults # to ${script_location}/versions. When using multiple version # directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" +# The path separator used here should be the separator specified by "version_path_separator" below. # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions # version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space -version_path_separator = os # default: use os.pathsep +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. # the output encoding used when revision files # are written from script.py.mako diff -Nru alembic-1.7.1/alembic/templates/multidb/README alembic-1.7.6/alembic/templates/multidb/README --- alembic-1.7.1/alembic/templates/multidb/README 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/multidb/README 2022-02-01 15:00:09.000000000 +0000 @@ -1 +1,12 @@ -Rudimentary multi-database configuration. \ No newline at end of file +Rudimentary multi-database configuration. + +Multi-DB isn't vastly different from generic. The primary difference is that it +will run the migrations N times (depending on how many databases you have +configured), providing one engine name and associated context for each run. + +That engine name will then allow the migration to restrict what runs within it to +just the appropriate migrations for that engine. You can see this behavior within +the mako template. + +In the provided configuration, you'll need to have `databases` provided in +alembic's config, and an `sqlalchemy.url` provided for each engine name. diff -Nru alembic-1.7.1/alembic/templates/pylons/alembic.ini.mako alembic-1.7.6/alembic/templates/pylons/alembic.ini.mako --- alembic-1.7.1/alembic/templates/pylons/alembic.ini.mako 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/templates/pylons/alembic.ini.mako 2022-02-01 15:00:09.000000000 +0000 @@ -35,16 +35,18 @@ # version location specification; This defaults # to ${script_location}/versions. When using multiple version # directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" +# The path separator used here should be the separator specified by "version_path_separator" below. # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions # version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space -version_path_separator = os # default: use os.pathsep +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. # the output encoding used when revision files # are written from script.py.mako @@ -63,4 +65,4 @@ pylons_config_file = ./development.ini -# that's it ! \ No newline at end of file +# that's it ! diff -Nru alembic-1.7.1/alembic/testing/assertions.py alembic-1.7.6/alembic/testing/assertions.py --- alembic-1.7.1/alembic/testing/assertions.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/assertions.py 2022-02-01 15:00:09.000000000 +0000 @@ -5,7 +5,6 @@ from typing import Dict from sqlalchemy import exc as sa_exc -from sqlalchemy import util from sqlalchemy.engine import default from sqlalchemy.testing.assertions import _expect_warnings from sqlalchemy.testing.assertions import eq_ # noqa @@ -85,12 +84,10 @@ ec.error = err success = True if msg is not None: - assert re.search( - msg, util.text_type(err), re.UNICODE - ), "%r !~ %s" % (msg, err) + assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}" if check_context and not are_we_already_in_a_traceback: _assert_proper_exception_context(err) - print(util.text_type(err).encode("utf-8")) + print(str(err).encode("utf-8")) # assert outside the block so it works for AssertionError too ! assert success, "Callable did not raise an exception" diff -Nru alembic-1.7.1/alembic/testing/fixtures.py alembic-1.7.6/alembic/testing/fixtures.py --- alembic-1.7.1/alembic/testing/fixtures.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/fixtures.py 2022-02-01 15:00:09.000000000 +0000 @@ -25,10 +25,9 @@ from ..migration import MigrationContext from ..operations import Operations from ..util import sqla_compat -from ..util.compat import string_types -from ..util.compat import text_type from ..util.sqla_compat import create_mock_engine from ..util.sqla_compat import sqla_14 +from ..util.sqla_compat import sqla_1x testing_config = configparser.ConfigParser() @@ -36,7 +35,10 @@ class TestBase(SQLAlchemyTestBase): - is_sqlalchemy_future = False + if sqla_1x: + is_sqlalchemy_future = False + else: + is_sqlalchemy_future = True @testing.fixture() def ops_context(self, migration_context): @@ -199,10 +201,10 @@ if not as_sql: def execute(stmt, *multiparam, **param): - if isinstance(stmt, string_types): + if isinstance(stmt, str): stmt = text(stmt) assert stmt.supports_execution - sql = text_type(stmt.compile(dialect=ctx_dialect)) + sql = str(stmt.compile(dialect=ctx_dialect)) buf.write(sql) @@ -245,6 +247,7 @@ ), "server defaults %r and %r didn't compare as equivalent" % (s1, s2) def tearDown(self): + sqla_compat._safe_rollback_connection_transaction(self.conn) with self.conn.begin(): self.metadata.drop_all(self.conn) self.conn.close() diff -Nru alembic-1.7.1/alembic/testing/__init__.py alembic-1.7.6/alembic/testing/__init__.py --- alembic-1.7.1/alembic/testing/__init__.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/__init__.py 2022-02-01 15:00:09.000000000 +0000 @@ -27,10 +27,3 @@ from .assertions import ne_ from .fixtures import TestBase from .util import resolve_lambda - -try: - from sqlalchemy.testing import asyncio -except ImportError: - pass -else: - asyncio.ENABLE_ASYNCIO = False diff -Nru alembic-1.7.1/alembic/testing/requirements.py alembic-1.7.6/alembic/testing/requirements.py --- alembic-1.7.1/alembic/testing/requirements.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/requirements.py 2022-02-01 15:00:09.000000000 +0000 @@ -84,6 +84,13 @@ ) @property + def sqlalchemy_1x(self): + return exclusions.skip_if( + lambda config: not util.sqla_1x, + "SQLAlchemy 1.x test", + ) + + @property def comments(self): return exclusions.only_if( lambda config: config.db.dialect.supports_comments diff -Nru alembic-1.7.1/alembic/testing/schemacompare.py alembic-1.7.6/alembic/testing/schemacompare.py --- alembic-1.7.1/alembic/testing/schemacompare.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/schemacompare.py 2022-02-01 15:00:09.000000000 +0000 @@ -1,5 +1,6 @@ +from itertools import zip_longest + from sqlalchemy import schema -from sqlalchemy import util class CompareTable: @@ -10,7 +11,7 @@ if self.table.name != other.name or self.table.schema != other.schema: return False - for c1, c2 in util.zip_longest(self.table.c, other.c): + for c1, c2 in zip_longest(self.table.c, other.c): if (c1 is None and c2 is not None) or ( c2 is None and c1 is not None ): @@ -86,7 +87,7 @@ ) if not r1: return False - for c1, c2 in util.zip_longest(self.constraint.columns, other.columns): + for c1, c2 in zip_longest(self.constraint.columns, other.columns): if (c1 is None and c2 is not None) or ( c2 is None and c1 is not None ): @@ -113,7 +114,7 @@ if not r1: return False - for c1, c2 in util.zip_longest(self.constraint.columns, other.columns): + for c1, c2 in zip_longest(self.constraint.columns, other.columns): if (c1 is None and c2 is not None) or ( c2 is None and c1 is not None ): @@ -141,7 +142,7 @@ if not r1: return False - for c1, c2 in util.zip_longest(self.constraint.columns, other.columns): + for c1, c2 in zip_longest(self.constraint.columns, other.columns): if (c1 is None and c2 is not None) or ( c2 is None and c1 is not None ): diff -Nru alembic-1.7.1/alembic/testing/suite/test_environment.py alembic-1.7.6/alembic/testing/suite/test_environment.py --- alembic-1.7.1/alembic/testing/suite/test_environment.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/suite/test_environment.py 2022-02-01 15:00:09.000000000 +0000 @@ -4,8 +4,11 @@ from ...testing import assert_raises from ...testing import config from ...testing import eq_ +from ...testing import is_ from ...testing import is_false +from ...testing import is_not_ from ...testing import is_true +from ...testing import ne_ from ...testing.fixtures import TestBase @@ -230,8 +233,29 @@ is_true(self.conn.in_transaction()) with context.autocommit_block(): - is_false(self.conn.in_transaction()) - + # in 1.x, self.conn is separate due to the + # execution_options call. however for future they are the + # same connection and there is a "transaction" block + # despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options[ + "isolation_level" + ], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get( + "isolation_level", None + ), + "AUTOCOMMIT", + ) is_true(self.conn.in_transaction()) is_false(self.conn.in_transaction()) @@ -244,7 +268,27 @@ is_false(self.conn.in_transaction()) with context.autocommit_block(): - is_false(self.conn.in_transaction()) + is_true(context.connection.in_transaction()) + + # in 1.x, self.conn is separate due to the execution_options + # call. however for future they are the same connection and there + # is a "transaction" block despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options["isolation_level"], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get("isolation_level", None), + "AUTOCOMMIT", + ) + is_false(self.conn.in_transaction()) def test_autocommit_block_transactional_ddl_sqlmode(self): diff -Nru alembic-1.7.1/alembic/testing/util.py alembic-1.7.6/alembic/testing/util.py --- alembic-1.7.1/alembic/testing/util.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/testing/util.py 2022-02-01 15:00:09.000000000 +0000 @@ -5,7 +5,9 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php +import re import types +from typing import Union def flag_combinations(*combinations): @@ -97,11 +99,28 @@ return decorate +def _safe_int(value: str) -> Union[int, str]: + try: + return int(value) + except: + return value + + def testing_engine(url=None, options=None, future=False): from sqlalchemy.testing import config from sqlalchemy.testing.engines import testing_engine + from sqlalchemy import __version__ + + _vers = tuple( + [_safe_int(x) for x in re.findall(r"(\d+|[abc]\d)", __version__)] + ) + sqla_1x = _vers < (2,) if not future: future = getattr(config._current.options, "future_engine", False) - kw = {"future": future} if future else {} + + if sqla_1x: + kw = {"future": future} if future else {} + else: + kw = {} return testing_engine(url, options, **kw) diff -Nru alembic-1.7.1/alembic/util/compat.py alembic-1.7.6/alembic/util/compat.py --- alembic-1.7.1/alembic/util/compat.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/util/compat.py 2022-02-01 15:00:09.000000000 +0000 @@ -1,6 +1,7 @@ import io import os import sys +from typing import Tuple from sqlalchemy.util import inspect_getfullargspec # noqa from sqlalchemy.util.compat import inspect_formatargspec # noqa @@ -11,10 +12,6 @@ py38 = sys.version_info >= (3, 8) py37 = sys.version_info >= (3, 7) -string_types = (str,) -binary_type = bytes -text_type = str - # produce a wrapper that allows encoded text to stream # into a given buffer, but doesn't close it. @@ -26,18 +23,30 @@ if py39: from importlib import resources as importlib_resources -else: - import importlib_resources # type:ignore[no-redef] # noqa - -if py38: from importlib import metadata as importlib_metadata + from importlib.metadata import EntryPoint else: + import importlib_resources # type:ignore[no-redef] # noqa import importlib_metadata # type:ignore[no-redef] # noqa + from importlib_metadata import EntryPoint # type:ignore # noqa -def importlib_metadata_get(group): +def importlib_metadata_get(group: str) -> Tuple[EntryPoint, ...]: ep = importlib_metadata.entry_points() if hasattr(ep, "select"): - return ep.select(group=group) + return ep.select(group=group) # type:ignore[attr-defined] else: return ep.get(group, ()) + + +def formatannotation_fwdref(annotation, base_module=None): + """the python 3.7 _formatannotation with an extra repr() for 3rd party + modules""" + + if getattr(annotation, "__module__", None) == "typing": + return repr(annotation).replace("typing.", "") + if isinstance(annotation, type): + if annotation.__module__ in ("builtins", base_module): + return annotation.__qualname__ + return repr(annotation.__module__ + "." + annotation.__qualname__) + return repr(annotation) diff -Nru alembic-1.7.1/alembic/util/__init__.py alembic-1.7.6/alembic/util/__init__.py --- alembic-1.7.1/alembic/util/__init__.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/util/__init__.py 2022-02-01 15:00:09.000000000 +0000 @@ -25,6 +25,7 @@ from .sqla_compat import has_computed from .sqla_compat import sqla_13 from .sqla_compat import sqla_14 +from .sqla_compat import sqla_1x if not sqla_13: diff -Nru alembic-1.7.1/alembic/util/langhelpers.py alembic-1.7.6/alembic/util/langhelpers.py --- alembic-1.7.1/alembic/util/langhelpers.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/util/langhelpers.py 2022-02-01 15:00:09.000000000 +0000 @@ -21,7 +21,6 @@ from sqlalchemy.util import unique_list # noqa from .compat import inspect_getfullargspec -from .compat import string_types _T = TypeVar("_T") @@ -209,7 +208,7 @@ def to_tuple(x, default=None): if x is None: return default - elif isinstance(x, string_types): + elif isinstance(x, str): return (x,) elif isinstance(x, Iterable): return tuple(x) @@ -241,7 +240,7 @@ def dispatch(self, obj: Any, qualifier: str = "default") -> Any: - if isinstance(obj, string_types): + if isinstance(obj, str): targets: Sequence = [obj] elif isinstance(obj, type): targets = obj.__mro__ diff -Nru alembic-1.7.1/alembic/util/messaging.py alembic-1.7.6/alembic/util/messaging.py --- alembic-1.7.1/alembic/util/messaging.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/util/messaging.py 2022-02-01 15:00:09.000000000 +0000 @@ -12,8 +12,6 @@ from sqlalchemy.engine import url from . import sqla_compat -from .compat import binary_type -from .compat import string_types log = logging.getLogger(__name__) @@ -37,7 +35,7 @@ def write_outstream(stream: TextIO, *text) -> None: encoding = getattr(stream, "encoding", "ascii") or "ascii" for t in text: - if not isinstance(t, binary_type): + if not isinstance(t, bytes): t = t.encode(encoding, "replace") t = t.decode(encoding) try: @@ -100,7 +98,7 @@ def format_as_comma(value: Optional[Union[str, "Iterable[str]"]]) -> str: if value is None: return "" - elif isinstance(value, string_types): + elif isinstance(value, str): return value elif isinstance(value, Iterable): return ", ".join(value) diff -Nru alembic-1.7.1/alembic/util/sqla_compat.py alembic-1.7.6/alembic/util/sqla_compat.py --- alembic-1.7.1/alembic/util/sqla_compat.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/alembic/util/sqla_compat.py 2022-02-01 15:00:09.000000000 +0000 @@ -23,8 +23,6 @@ from sqlalchemy.sql.elements import TextClause from sqlalchemy.sql.visitors import traverse -from . import compat - if TYPE_CHECKING: from sqlalchemy import Index from sqlalchemy import Table @@ -57,6 +55,16 @@ ) sqla_13 = _vers >= (1, 3) sqla_14 = _vers >= (1, 4) +sqla_14_26 = _vers >= (1, 4, 26) + + +if sqla_14: + # when future engine merges, this can be again based on version string + from sqlalchemy.engine import Connection as legacy_connection + + sqla_1x = not hasattr(legacy_connection, "commit") +else: + sqla_1x = True try: from sqlalchemy import Computed # noqa @@ -121,6 +129,22 @@ return connection.begin() +def _safe_commit_connection_transaction( + connection: "Connection", +) -> None: + transaction = _get_connection_transaction(connection) + if transaction: + transaction.commit() + + +def _safe_rollback_connection_transaction( + connection: "Connection", +) -> None: + transaction = _get_connection_transaction(connection) + if transaction: + transaction.rollback() + + def _get_connection_in_transaction(connection: Optional["Connection"]) -> bool: try: in_transaction = connection.in_transaction # type: ignore @@ -133,9 +157,9 @@ def _copy(schema_item: _CE, **kw) -> _CE: if hasattr(schema_item, "_copy"): - return schema_item._copy(**kw) + return schema_item._copy(**kw) # type: ignore[union-attr] else: - return schema_item.copy(**kw) + return schema_item.copy(**kw) # type: ignore[union-attr] def _get_connection_transaction( @@ -226,6 +250,32 @@ return inspector.reflecttable(table, None) +def _resolve_for_variant(type_, dialect): + if _type_has_variants(type_): + base_type, mapping = _get_variant_mapping(type_) + return mapping.get(dialect.name, base_type) + else: + return type_ + + +if hasattr(sqltypes.TypeEngine, "_variant_mapping"): + + def _type_has_variants(type_): + return bool(type_._variant_mapping) + + def _get_variant_mapping(type_): + return type_, type_._variant_mapping + + +else: + + def _type_has_variants(type_): + return type(type_) is sqltypes.Variant + + def _get_variant_mapping(type_): + return type_.impl, type_.mapping + + def _fk_spec(constraint): source_columns = [ constraint.columns[key].name for key in constraint.column_keys @@ -294,7 +344,7 @@ table: "Table", text_: Union[str, "TextClause", "ColumnElement"] ) -> Union["ColumnElement", "Column"]: """a workaround for the Index construct's severe lack of flexibility""" - if isinstance(text_, compat.string_types): + if isinstance(text_, str): c = Column(text_, sqltypes.NULLTYPE) table.append_column(c) return c diff -Nru alembic-1.7.1/debian/changelog alembic-1.7.6/debian/changelog --- alembic-1.7.1/debian/changelog 2021-09-29 19:32:48.000000000 +0000 +++ alembic-1.7.6/debian/changelog 2022-02-21 09:59:51.000000000 +0000 @@ -1,8 +1,22 @@ -alembic (1.7.1-3) unstable; urgency=medium +alembic (1.7.6-1) unstable; urgency=medium - * Uploading to unstable. + [ Debian Janitor ] + * Remove constraints unnecessary since buster: + + Build-Depends: Drop versioned constraint on dh-python. + + Build-Depends-Indep: Drop versioned constraint on python3-sqlalchemy. + + alembic: Drop versioned constraint on python-alembic and python3-alembic + in Replaces. + + alembic: Drop versioned constraint on python-alembic and python3-alembic + in Breaks. + + python3-alembic: Drop versioned constraint on alembic in Breaks. - -- Thomas Goirand Wed, 29 Sep 2021 21:32:48 +0200 + [ Thomas Goirand ] + * Fix debian/watch to use github instead of bitbucket. + * Update d/control and d/copyright upstream URLs to point to github. + * New upstream release: + - Fixes FTBFS with newer SQLAlchemy (Closes: #1006044). + + -- Thomas Goirand Mon, 21 Feb 2022 10:59:51 +0100 alembic (1.7.1-2) experimental; urgency=medium diff -Nru alembic-1.7.1/debian/control alembic-1.7.6/debian/control --- alembic-1.7.1/debian/control 2021-09-29 19:32:48.000000000 +0000 +++ alembic-1.7.6/debian/control 2022-02-21 09:59:51.000000000 +0000 @@ -7,7 +7,7 @@ Ondřej Nový , Build-Depends: debhelper-compat (= 13), - dh-python (>= 3.20180927~), + dh-python, python3-all, python3-changelog, python3-setuptools, @@ -19,11 +19,11 @@ python3-mako, python3-mock, python3-pytest, - python3-sqlalchemy (>= 1.1.0), + python3-sqlalchemy, Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/python-team/packages/alembic Vcs-Git: https://salsa.debian.org/python-team/packages/alembic.git -Homepage: https://bitbucket.org/zzzeek/alembic +Homepage: https://github.com/sqlalchemy/alembic Testsuite: autopkgtest-pkg-python Rules-Requires-Root: no @@ -34,12 +34,6 @@ ${misc:Depends}, ${python3:Depends}, ${sphinxdoc:Depends}, -Breaks: - python-alembic (<< 0.8.8-3), - python3-alembic (<< 0.8.8-3), -Replaces: - python-alembic (<< 0.8.8-3), - python3-alembic (<< 0.8.8-3), Description: lightweight database migration tool for SQLAlchemy Alembic is a new database migration tool, written by the author of SQLAlchemy. A migration tool offers the following functionality: @@ -64,8 +58,6 @@ ${misc:Depends}, ${python3:Depends}, ${sphinxdoc:Depends}, -Breaks: - alembic (<< 0.8.8-3), Description: lightweight database migration tool for SQLAlchemy - Python module Alembic is a new database migration tool, written by the author of SQLAlchemy. A migration tool offers the following functionality: diff -Nru alembic-1.7.1/debian/copyright alembic-1.7.6/debian/copyright --- alembic-1.7.1/debian/copyright 2021-09-29 19:32:48.000000000 +0000 +++ alembic-1.7.6/debian/copyright 2022-02-21 09:59:51.000000000 +0000 @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: alembic Upstream-Contact: Mike Bayer -Source: https://pypi.python.org/pypi/alembic +Source: https://github.com/sqlalchemy/alembic Files: * Copyright: (c) 2009-2019, Michael Bayer diff -Nru alembic-1.7.1/debian/watch alembic-1.7.6/debian/watch --- alembic-1.7.1/debian/watch 2021-09-29 19:32:48.000000000 +0000 +++ alembic-1.7.6/debian/watch 2022-02-21 09:59:51.000000000 +0000 @@ -1,3 +1,3 @@ version=3 opts=uversionmangle=s/_/./g \ -https://bitbucket.org/zzzeek/alembic/downloads?tab=tags .*/rel_(\d\S+)\.tar\.gz +https://github.com/sqlalchemy/alembic/tags .*/rel_(\d\S+)\.tar\.gz diff -Nru alembic-1.7.1/docs/build/api/autogenerate.rst alembic-1.7.6/docs/build/api/autogenerate.rst --- alembic-1.7.1/docs/build/api/autogenerate.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/api/autogenerate.rst 2022-02-01 15:00:09.000000000 +0000 @@ -380,7 +380,7 @@ Given the above, the following guidelines should be considered when the ``env.py`` script calls upon :meth:`.MigrationContext.run_migrations` -mutiple times when running autogenerate: +multiple times when running autogenerate: * If the ``process_revision_directives`` hook aims to **add elements based on inspection of the current database / diff -Nru alembic-1.7.1/docs/build/api/config.rst alembic-1.7.6/docs/build/api/config.rst --- alembic-1.7.1/docs/build/api/config.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/api/config.rst 2022-02-01 15:00:09.000000000 +0000 @@ -18,7 +18,7 @@ with the actual script files in a migration environment * to create an :class:`.EnvironmentContext`, which allows you to actually run the ``env.py`` module within the migration environment -* to programatically run any of the commands in the :ref:`alembic.command.toplevel` +* to programmatically run any of the commands in the :ref:`alembic.command.toplevel` module. The :class:`.Config` is *not* needed for these cases: diff -Nru alembic-1.7.1/docs/build/autogenerate.rst alembic-1.7.6/docs/build/autogenerate.rst --- alembic-1.7.1/docs/build/autogenerate.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/autogenerate.rst 2022-02-01 15:00:09.000000000 +0000 @@ -471,7 +471,7 @@ The above custom type has a long and cumbersome name based on the use of ``__module__`` directly, which also implies that lots of imports would -be needed in order to accomodate lots of types. For this reason, it is +be needed in order to accommodate lots of types. For this reason, it is recommended that user-defined types used in migration scripts be made available from a single module. Suppose we call it ``myapp.migration_types``:: diff -Nru alembic-1.7.1/docs/build/batch.rst alembic-1.7.6/docs/build/batch.rst --- alembic-1.7.1/docs/build/batch.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/batch.rst 2022-02-01 15:00:09.000000000 +0000 @@ -243,11 +243,14 @@ recreate operation. For **unnamed** CHECK constraints, these are still not automatically included -as part of the batch process as they are often present due to the use of the +as part of the batch process. Note that this limitation **includes** the CHECK +constraints generated by the :class:`~sqlalchemy.types.Boolean` or :class:`~sqlalchemy.types.Enum` datatypes, which up through SQLAlchemy 1.3 would generate CHECK constraints -automatically and cannot be tracked to the reflected table. Therefore unnamed -constraints can be stated explicitly if they are to be included in the +automatically and cannot be tracked to the reflected table, assuming they are +generated in an unnamed way. + +Unnamed constraints can be stated explicitly if they are to be included in the recreated table:: with op.batch_alter_table("some_table", table_args=[ @@ -256,10 +259,16 @@ batch_op.add_column(Column('foo', Integer)) batch_op.drop_column('bar') -Note this only includes CHECK constraints that are explicitly stated -as part of the table definition, not the CHECK constraints that are generated -by datatypes such as :class:`~sqlalchemy.types.Boolean` or -:class:`~sqlalchemy.types.Enum`. +The above step needs only be taken for CHECK constraints that are explicitly stated +as part of the table definition. + +For CHECK constraints that are generated by datatypes such as +:class:`~sqlalchemy.types.Boolean` or :class:`~sqlalchemy.types.Enum`, the type +objects themselves **must be named** in order for their CHECK constraints to be +included in the batch process. Boolean and Enum datatypes that do not +have the ``.name`` attribute set will **not** have CHECK constraints +regenerated. This name can be set by specifying the ``.name`` parameter +or by using a named Python ``Enum`` object as the source of enumeration. Dealing with Referencing Foreign Keys ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -353,7 +362,7 @@ This mode is safe to use in all cases, as the :meth:`.Operations.batch_alter_table` directive by default only takes place for SQLite; other backends will -behave just as they normally do in the absense of the batch directives. +behave just as they normally do in the absence of the batch directives. Note that autogenerate support does not include "offline" mode, where the :paramref:`.Operations.batch_alter_table.copy_from` parameter is used. diff -Nru alembic-1.7.1/docs/build/branches.rst alembic-1.7.6/docs/build/branches.rst --- alembic-1.7.1/docs/build/branches.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/branches.rst 2022-02-01 15:00:09.000000000 +0000 @@ -521,11 +521,12 @@ to multiple directories; we'll also state the current ``versions`` directory as one of them:: + # A separator for the location paths must be defined first. + version_path_separator = os # Use os.pathsep. # version location specification; this defaults # to foo/versions. When using multiple version # directories, initial revisions must be specified with --version-path - version_path_separator = space - version_locations = %(here)s/model/networking %(here)s/alembic/versions + version_locations = %(here)s/model/networking:%(here)s/alembic/versions The new directory ``%(here)s/model/networking`` is in terms of where the ``alembic.ini`` file is, as we are using the symbol ``%(here)s`` which diff -Nru alembic-1.7.1/docs/build/changelog.rst alembic-1.7.6/docs/build/changelog.rst --- alembic-1.7.1/docs/build/changelog.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/changelog.rst 2022-02-01 15:00:09.000000000 +0000 @@ -4,6 +4,119 @@ ========== .. changelog:: + :version: 1.7.6 + :released: February 1, 2022 + + .. change:: + :tags: bug, batch, regression + :tickets: 982 + + Fixed regression where usage of a ``with_variant()`` datatype in + conjunction with the ``existing_type`` option of ``op.alter_column()`` + under batch mode would lead to an internal exception. + + .. change:: + :tags: usecase, commands + :tickets: 964 + + Add a new command ``alembic ensure_version``, which will ensure that the + Alembic version table is present in the target database, but does not + alter its contents. Pull request courtesy Kai Mueller. + + .. change:: + :tags: bug, autogenerate + + Implemented support for recognizing and rendering SQLAlchemy "variant" + types going forward into SQLAlchemy 2.0, where the architecture of + "variant" datatypes will be changing. + + + .. change:: + :tags: bug, mysql, autogenerate + :tickets: 968 + + Added a rule to the MySQL impl so that the translation between JSON / + LONGTEXT is accommodated by autogenerate, treating LONGTEXT from the server + as equivalent to an existing JSON in the model. + + .. change:: + :tags: mssql + + Removed a warning raised by SQLAlchemy when dropping constraints + on MSSQL regarding statement caching. + +.. changelog:: + :version: 1.7.5 + :released: November 11, 2021 + + .. change:: + :tags: bug, tests + + Adjustments to the test suite to accommodate for error message changes + occurring as of SQLAlchemy 1.4.27. + +.. changelog:: + :version: 1.7.4 + :released: October 6, 2021 + + .. change:: + :tags: bug, regression + :tickets: 934 + + Fixed a regression that prevented the use of post write hooks + on python version lower than 3.9 + + .. change:: + :tags: bug, environment + :tickets: 944 + + Fixed issue where the :meth:`.MigrationContext.autocommit_block` feature + would fail to function when using a SQLAlchemy engine using 2.0 future + mode. + + +.. changelog:: + :version: 1.7.3 + :released: September 17, 2021 + + .. change:: + :tags: bug, mypy + :tickets: 914 + + Fixed type annotations for the "constraint_name" argument of operations + ``create_primary_key()``, ``create_foreign_key()``. Pull request courtesy + TilmanK. + + +.. changelog:: + :version: 1.7.2 + :released: September 17, 2021 + + .. change:: + :tags: bug, typing + :tickets: 900 + + Added missing attributes from context stubs. + + .. change:: + :tags: bug, mypy + :tickets: 897 + + Fixed an import in one of the .pyi files that was triggering an + assertion error in some versions of mypy. + + .. change:: + :tags: bug, regression, ops + :tickets: 920 + + Fixed issue where registration of custom ops was prone to failure due to + the registration process running ``exec()`` on generated code that as of + the 1.7 series includes pep-484 annotations, which in the case of end user + code would result in name resolution errors when the exec occurs. The logic + in question has been altered so that the annotations are rendered as + forward references so that the ``exec()`` can proceed. + +.. changelog:: :version: 1.7.1 :released: August 30, 2021 @@ -383,7 +496,7 @@ Add async template to Alembic to bootstrap environments that use async DBAPI. Updated the cookbook to include a migration guide - on how to adapt an existing enviroment for use with DBAPI drivers. + on how to adapt an existing environment for use with DBAPI drivers. .. changelog:: :version: 1.5.5 @@ -563,7 +676,7 @@ **before** the SQLAlchemy reflection process takes place, and notably includes explicit support for passing each schema name when :paramref:`.EnvironmentContext.configure.include_schemas` is set to True. - This is most important especially for enviroments that make use of + This is most important especially for environments that make use of :paramref:`.EnvironmentContext.configure.include_schemas` where schemas are actually databases (e.g. MySQL) in order to prevent reflection sweeps of the entire server. @@ -1057,7 +1170,7 @@ Added new feature :meth:`.MigrationContext.autocommit_block`, a special directive which will provide for a non-transactional block inside of a - migration script. The feature requres that: the database driver + migration script. The feature requires that: the database driver (e.g. DBAPI) supports the AUTOCOMMIT isolation mode. The directive also necessarily needs to COMMIT the existing transaction in progress in order to enter autocommit mode. @@ -1317,7 +1430,7 @@ :tags: bug, py3k :tickets: 563 - Replaced the Python compatbility routines for ``getargspec()`` with a fully + Replaced the Python compatibility routines for ``getargspec()`` with a fully vendored version based on ``getfullargspec()`` from Python 3.3. Originally, Python was emitting deprecation warnings for this function in Python 3.8 alphas. While this change was reverted, it was observed that @@ -1885,7 +1998,7 @@ Added a new configuration option ``timezone``, a string timezone name that will be applied to the create date timestamp rendered - inside the revision file as made availble to the ``file_template`` used + inside the revision file as made available to the ``file_template`` used to generate the revision filename. Note this change adds the ``python-dateutil`` package as a dependency. @@ -2017,7 +2130,7 @@ :tags: bug, operations Fixed bug in :func:`.ops.create_foreign_key` where the internal table - representation would not be created properly if the foriegn key referred + representation would not be created properly if the foreign key referred to a table in a different schema of the same name. Pull request courtesy Konstantin Lebedev. @@ -2243,7 +2356,7 @@ Added a fix to Postgresql server default comparison which first checks if the text of the default is identical to the original, before attempting - to actually run the default. This accomodates for default-generation + to actually run the default. This accommodates for default-generation functions that generate a new value each time such as a uuid function. .. change:: @@ -3797,7 +3910,7 @@ .. change:: :tags: bug - Fixes to Py3k in-place compatibity regarding output encoding and related; + Fixes to Py3k in-place compatibility regarding output encoding and related; the use of the new io.* package introduced some incompatibilities on Py2k. These should be resolved, due to the introduction of new adapter types for translating from io.* to Py2k file types, StringIO types. @@ -4078,7 +4191,7 @@ :tickets: 99 Fixed bug where autogenerate would fail if a Column - to be added to a table made use of the ".key" paramter. + to be added to a table made use of the ".key" parameter. .. change:: :tags: bug, sqlite @@ -4100,7 +4213,7 @@ :tickets: 96 Added a workaround to setup.py to prevent - "NoneType" error from occuring when + "NoneType" error from occurring when "setup.py test" is run. .. change:: @@ -4312,7 +4425,7 @@ :tags: bug :tickets: 66 - Improved error message when specifiying + Improved error message when specifying non-ordered revision identifiers to cover the case when the "higher" rev is None, improved message overall. diff -Nru alembic-1.7.1/docs/build/conf.py alembic-1.7.6/docs/build/conf.py --- alembic-1.7.1/docs/build/conf.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/conf.py 2022-02-01 15:00:09.000000000 +0000 @@ -76,7 +76,7 @@ # General information about the project. project = u"Alembic" -copyright = u"2010-2021, Mike Bayer" # noqa +copyright = u"2010-2022, Mike Bayer" # noqa # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -85,8 +85,8 @@ # The short X.Y version. version = alembic.__version__ # The full version, including alpha/beta/rc tags. -release = "1.7.1" -release_date = "August 30, 2021" +release = "1.7.6" +release_date = "February 1, 2022" # The language for content autogenerated by Sphinx. Refer to documentation diff -Nru alembic-1.7.1/docs/build/cookbook.rst alembic-1.7.6/docs/build/cookbook.rst --- alembic-1.7.1/docs/build/cookbook.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/cookbook.rst 2022-02-01 15:00:09.000000000 +0000 @@ -233,30 +233,21 @@ prefix='sqlalchemy.', poolclass=pool.NullPool) - # when connectable is already a Connection object, calling - # connect() gives us a *branched connection*. - - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=target_metadata - ) + context.configure( + connection=connectable, + target_metadata=target_metadata + ) - with context.begin_transaction(): - context.run_migrations() + with context.begin_transaction(): + context.run_migrations() -.. topic:: Branched Connections +.. versionchanged:: 1.4 - Note that we are calling the ``connect()`` method, **even if we are - using a** :class:`~sqlalchemy.engine.Connection` **object to start with**. - The effect this has when calling :meth:`~sqlalchemy.engine.Connection.connect` - is that SQLAlchemy passes us a **branch** of the original connection; it - is in every way the same as the :class:`~sqlalchemy.engine.Connection` - we started with, except it provides **nested scope**; the - context we have here as well as the - :meth:`~sqlalchemy.engine.Connection.close` method of this branched - connection doesn't actually close the outer connection, which stays - active for continued use. + Prior to this version, we used a "branched connection", by calling + :meth:`~sqlalchemy.engine.Connection.connect`. + This is now deprecated and unnecessary, + since we no longer have to guess if the given "connection" + is an ``Engine`` or ``Connection``, it is always a ``Connection``. .. _replaceable_objects: @@ -1464,7 +1455,9 @@ ``run_migrations_online`` will need to be updated to be something like the example below:: import asyncio - + + from sqlalchemy.ext.asyncio import AsyncEngine + # ... no change required to the rest of the code @@ -1494,6 +1487,8 @@ async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) + await connectable.dispose() + if context.is_offline_mode(): run_migrations_offline() diff -Nru alembic-1.7.1/docs/build/requirements.txt alembic-1.7.6/docs/build/requirements.txt --- alembic-1.7.1/docs/build/requirements.txt 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/requirements.txt 2022-02-01 15:00:09.000000000 +0000 @@ -4,3 +4,5 @@ python-dateutil # because there's a dependency in pyfiles.py Mako +importlib-metadata;python_version<"3.8" +importlib-resources;python_version<"3.9" \ No newline at end of file diff -Nru alembic-1.7.1/docs/build/tutorial.rst alembic-1.7.6/docs/build/tutorial.rst --- alembic-1.7.1/docs/build/tutorial.rst 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/docs/build/tutorial.rst 2022-02-01 15:00:09.000000000 +0000 @@ -118,9 +118,9 @@ ===================== Alembic placed a file ``alembic.ini`` into the current directory. This is a file that the ``alembic`` -script looks for when invoked. This file can be anywhere, either in the same directory -from which the ``alembic`` script will normally be invoked, or if in a different directory, can -be specified by using the ``--config`` option to the ``alembic`` runner. +script looks for when invoked. This file can exist in a different directory, with the location to it +specified by either the ``--config`` option for the ``alembic`` runner or the ``ALEMBIC_CONFIG`` +environment variable (the former takes precedence). The file generated with the "generic" configuration looks like:: @@ -162,16 +162,18 @@ # version location specification; This defaults # to ${script_location}/versions. When using multiple version # directories, initial revisions must be specified with --version-path. - # The path separator used here should be the separator specified by "version_path_separator" + # The path separator used here should be the separator specified by "version_path_separator" below. # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions # version path separator; As mentioned above, this is the character used to split - # version_locations. Valid values are: + # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. + # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. + # Valid values for version_path_separator are: # # version_path_separator = : # version_path_separator = ; # version_path_separator = space - version_path_separator = os # default: use os.pathsep + version_path_separator = os # Use os.pathsep. Default configuration used for new projects. # the output encoding used when revision files # are written from script.py.mako @@ -179,7 +181,8 @@ sqlalchemy.url = driver://user:pass@localhost/dbname - # post_write_hooks defines scripts or Python functions that are run + # [post_write_hooks] + # This section defines scripts or Python functions that are run # on newly generated revision scripts. See the documentation for further # detail and examples @@ -306,6 +309,10 @@ allow revisions to exist in multiple directories simultaneously. See :ref:`multiple_bases` for examples. +* ``version_path_separator`` - a separator of ``version_locations`` paths. + It should be defined if multiple ``version_locations`` is used. + See :ref:`multiple_bases` for examples. + * ``output_encoding`` - the encoding to use when Alembic writes the ``script.py.mako`` file into a new migration file. Defaults to ``'utf-8'``. diff -Nru alembic-1.7.1/.github/workflows/run-on-pr.yaml alembic-1.7.6/.github/workflows/run-on-pr.yaml --- alembic-1.7.1/.github/workflows/run-on-pr.yaml 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/.github/workflows/run-on-pr.yaml 2022-02-01 15:00:09.000000000 +0000 @@ -1,10 +1,10 @@ name: Run tests on a pr on: - # run on pull request to master excluding changes that are only on doc or example folders + # run on pull request to main excluding changes that are only on doc or example folders pull_request: branches: - - master + - main paths-ignore: - "docs/**" @@ -22,10 +22,11 @@ os: - "ubuntu-latest" python-version: - - "3.9" + - "3.10" sqlalchemy: - sqla13 - - sqlamaster + - sqla14 + - sqlamain # abort all jobs as soon as one fails fail-fast: true @@ -48,3 +49,34 @@ - name: Run tests run: tox -e py-${{ matrix.sqlalchemy }} + + run-pep484: + name: pep484-${{ matrix.python-version }}-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - "ubuntu-latest" + python-version: + - "3.10" + + fail-fast: false + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.architecture }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade tox setuptools + pip list + + - name: Run pep484 + run: tox -e pep484 diff -Nru alembic-1.7.1/.github/workflows/run-test.yaml alembic-1.7.6/.github/workflows/run-test.yaml --- alembic-1.7.1/.github/workflows/run-test.yaml 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/.github/workflows/run-test.yaml 2022-02-01 15:00:09.000000000 +0000 @@ -1,10 +1,10 @@ name: Run tests on: - # run on push in master or rel_* branches excluding changes are only on doc or example folders + # run on push in main or rel_* branches excluding changes are only on doc or example folders push: branches: - - master + - main - "rel_*" # branches used to test the workflow - "workflow_test_*" @@ -31,9 +31,16 @@ - "3.7" - "3.8" - "3.9" + - "3.10" sqlalchemy: - sqla13 - - sqlamaster + - sqla14 + - sqlamain + + exclude: + # main no longer support 3.6 + - sqlalchemy: sqlamain + python-version: "3.6" fail-fast: false @@ -56,3 +63,35 @@ - name: Run tests run: tox -e py-${{ matrix.sqlalchemy }} + + run-pep484: + name: pep484-${{ matrix.python-version }}-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - "ubuntu-latest" + python-version: + - "3.9" + - "3.10" + + fail-fast: false + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.architecture }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade tox setuptools + pip list + + - name: Run tox pep484 + run: tox -e pep484 diff -Nru alembic-1.7.1/.gitreview alembic-1.7.6/.gitreview --- alembic-1.7.1/.gitreview 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/.gitreview 2022-02-01 15:00:09.000000000 +0000 @@ -1,6 +1,6 @@ [gerrit] host=gerrit.sqlalchemy.org project=sqlalchemy/alembic -defaultbranch=master +defaultbranch=main port=29418 diff -Nru alembic-1.7.1/LICENSE alembic-1.7.6/LICENSE --- alembic-1.7.1/LICENSE 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/LICENSE 2022-02-01 15:00:09.000000000 +0000 @@ -1,4 +1,4 @@ -Copyright 2009-2021 Michael Bayer. +Copyright 2009-2022 Michael Bayer. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff -Nru alembic-1.7.1/setup.cfg alembic-1.7.6/setup.cfg --- alembic-1.7.1/setup.cfg 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/setup.cfg 2022-02-01 15:00:09.000000000 +0000 @@ -42,7 +42,7 @@ install_requires = SQLAlchemy>=1.3.0 Mako - importlib-metadata;python_version<"3.8" + importlib-metadata;python_version<"3.9" importlib-resources;python_version<"3.9" [options.extras_require] diff -Nru alembic-1.7.1/tests/requirements.py alembic-1.7.6/tests/requirements.py --- alembic-1.7.1/tests/requirements.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/requirements.py 2022-02-01 15:00:09.000000000 +0000 @@ -1,3 +1,4 @@ +from sqlalchemy import exc as sqla_exc from sqlalchemy import text from alembic.testing import exclusions @@ -97,11 +98,23 @@ @property def fk_ondelete_is_reflected(self): - return exclusions.fails_on(["mssql"]) + def go(config): + if exclusions.against(config, "mssql"): + return not sqla_compat.sqla_14_26 + else: + return False + + return exclusions.fails_if(go) @property def fk_onupdate_is_reflected(self): - return self.fk_onupdate + exclusions.fails_on(["mssql"]) + def go(config): + if exclusions.against(config, "mssql"): + return not sqla_compat.sqla_14_26 + else: + return False + + return self.fk_onupdate + exclusions.fails_if(go) @property def fk_onupdate(self): @@ -289,6 +302,54 @@ return False @property + def json_type(self): + return exclusions.only_on( + [ + lambda config: exclusions.against(config, "mysql") + and ( + ( + not config.db.dialect._is_mariadb + and exclusions.against(config, "mysql >= 5.7") + ) + or ( + config.db.dialect._mariadb_normalized_version_info + >= (10, 2, 7) + ) + ), + "mariadb>=10.2.7", + "postgresql >= 9.3", + self._sqlite_json, + self._mssql_json, + ] + ) + + def _mssql_json(self, config): + if not sqla_compat.sqla_14: + return False + else: + return exclusions.against(config, "mssql") + + def _sqlite_json(self, config): + if not sqla_compat.sqla_14: + return False + elif not exclusions.against(config, "sqlite >= 3.9"): + return False + else: + with config.db.connect() as conn: + try: + return ( + conn.execute( + text( + """select json_extract('{"foo": "bar"}', """ + """'$."foo"')""" + ) + ).scalar() + == "bar" + ) + except sqla_exc.DBAPIError: + return False + + @property def identity_columns(self): # TODO: in theory if these could come from SQLAlchemy dialects # that would be helpful diff -Nru alembic-1.7.1/tests/test_autogen_diffs.py alembic-1.7.6/tests/test_autogen_diffs.py --- alembic-1.7.1/tests/test_autogen_diffs.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_autogen_diffs.py 2022-02-01 15:00:09.000000000 +0000 @@ -15,6 +15,7 @@ from sqlalchemy import inspect from sqlalchemy import INTEGER from sqlalchemy import Integer +from sqlalchemy import JSON from sqlalchemy import LargeBinary from sqlalchemy import MetaData from sqlalchemy import Numeric @@ -927,6 +928,8 @@ (String(32),), (LargeBinary(),), (Unicode(32),), + (JSON(), config.requirements.json_type), + (mysql.LONGTEXT(), config.requirements.mysql), (Enum("one", "two", "three", name="the_enum"),), ) def test_introspected_columns_match_metadata_columns(self, cola): @@ -965,7 +968,7 @@ mysql.VARCHAR(200, charset="latin1"), mysql.VARCHAR(200, charset="utf-8"), True, - config.requirements.mysql + config.requirements.sqlalchemy_13, + config.requirements.mysql, ), ( String(255, collation="utf8_bin"), @@ -977,7 +980,7 @@ String(255, collation="utf8_bin"), String(255, collation="latin1_bin"), True, - config.requirements.mysql + config.requirements.sqlalchemy_13, + config.requirements.mysql, ), ) def test_string_comparisons(self, cola, colb, expect_changes): diff -Nru alembic-1.7.1/tests/test_batch.py alembic-1.7.6/tests/test_batch.py --- alembic-1.7.1/tests/test_batch.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_batch.py 2022-02-01 15:00:09.000000000 +0000 @@ -39,6 +39,7 @@ from alembic.testing import TestBase from alembic.testing.fixtures import op_fixture from alembic.util import exc as alembic_exc +from alembic.util.sqla_compat import _safe_commit_connection_transaction from alembic.util.sqla_compat import _select from alembic.util.sqla_compat import has_computed from alembic.util.sqla_compat import has_identity @@ -1137,6 +1138,30 @@ "ALTER TABLE _alembic_tmp_foo RENAME TO foo", ) + def test_change_name_from_existing_variant_type(self): + """test #982""" + context = self._fixture() + self.table.append_column( + Column("y", Text().with_variant(Text(10000), "mysql")) + ) + + with self.op.batch_alter_table( + "foo", copy_from=self.table + ) as batch_op: + batch_op.alter_column( + column_name="y", + new_column_name="q", + existing_type=Text().with_variant(Text(10000), "mysql"), + ) + context.assert_( + "CREATE TABLE _alembic_tmp_foo (id INTEGER NOT NULL, " + "data VARCHAR(50), x INTEGER, q TEXT, PRIMARY KEY (id))", + "INSERT INTO _alembic_tmp_foo (id, data, x, q) " + "SELECT foo.id, foo.data, foo.x, foo.y FROM foo", + "DROP TABLE foo", + "ALTER TABLE _alembic_tmp_foo RENAME TO foo", + ) + def test_change_type_to_schematype(self): context = self._fixture() self.table.append_column(Column("y", Integer)) @@ -1282,6 +1307,20 @@ context = MigrationContext.configure(self.conn) self.op = Operations(context) + def tearDown(self): + # why commit? because SQLite has inconsistent treatment + # of transactional DDL. A test that runs CREATE TABLE and then + # ALTER TABLE to change the name of that table, will end up + # committing the CREATE TABLE but not the ALTER. As batch mode + # does this with a temp table name that's not even in the + # metadata collection, we don't have an explicit drop for it + # (though we could do that too). calling commit means the + # ALTER will go through and the drop_all() will then catch it. + _safe_commit_connection_transaction(self.conn) + with self.conn.begin(): + self.metadata.drop_all(self.conn) + self.conn.close() + @contextmanager def _sqlite_referential_integrity(self): self.conn.exec_driver_sql("PRAGMA foreign_keys=ON") @@ -1385,7 +1424,7 @@ type_=Integer, existing_type=Boolean(create_constraint=True, name="ck1"), ) - insp = inspect(config.db) + insp = inspect(self.conn) eq_( [ @@ -1440,7 +1479,7 @@ batch_op.drop_column( "x", existing_type=Boolean(create_constraint=True, name="ck1") ) - insp = inspect(config.db) + insp = inspect(self.conn) assert "x" not in (c["name"] for c in insp.get_columns("hasbool")) @@ -1450,7 +1489,7 @@ batch_op.alter_column( "x", type_=Boolean(create_constraint=True, name="ck1") ) - insp = inspect(config.db) + insp = inspect(self.conn) if exclusions.against(config, "sqlite"): eq_( @@ -1471,14 +1510,6 @@ [Integer], ) - def tearDown(self): - in_t = getattr(self.conn, "in_transaction", lambda: False) - if in_t(): - self.conn.rollback() - with self.conn.begin(): - self.metadata.drop_all(self.conn) - self.conn.close() - def _assert_data(self, data, tablename="foo"): res = self.conn.execute(text("select * from %s" % tablename)) if sqla_14: @@ -1492,7 +1523,7 @@ batch_op.alter_column("data", type_=String(30)) batch_op.create_index("ix_data", ["data"]) - insp = inspect(config.db) + insp = inspect(self.conn) eq_( set( (ix["name"], tuple(ix["column_names"])) @@ -1734,7 +1765,7 @@ ) def _assert_table_comment(self, tname, comment): - insp = inspect(config.db) + insp = inspect(self.conn) tcomment = insp.get_table_comment(tname) eq_(tcomment, {"text": comment}) @@ -1794,7 +1825,7 @@ self._assert_table_comment("foo", None) def _assert_column_comment(self, tname, cname, comment): - insp = inspect(config.db) + insp = inspect(self.conn) cols = {col["name"]: col for col in insp.get_columns(tname)} eq_(cols[cname]["comment"], comment) @@ -2037,7 +2068,7 @@ ] ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data", "x", "data2"], ) @@ -2063,7 +2094,7 @@ ] ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data", "x", "data2"], ) @@ -2084,7 +2115,7 @@ tablename="nopk", ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data", "x"], ) @@ -2104,7 +2135,7 @@ ] ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data2", "data", "x"], ) @@ -2124,7 +2155,7 @@ ] ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data", "data2", "x"], ) @@ -2158,12 +2189,12 @@ ] ) eq_( - [col["name"] for col in inspect(config.db).get_columns("foo")], + [col["name"] for col in inspect(self.conn).get_columns("foo")], ["id", "data", "x", "data2"], ) def test_create_drop_index(self): - insp = inspect(config.db) + insp = inspect(self.conn) eq_(insp.get_indexes("foo"), []) with self.op.batch_alter_table("foo", recreate="always") as batch_op: @@ -2178,8 +2209,7 @@ {"id": 5, "data": "d5", "x": 9}, ] ) - - insp = inspect(config.db) + insp = inspect(self.conn) eq_( [ dict( @@ -2195,7 +2225,7 @@ with self.op.batch_alter_table("foo", recreate="always") as batch_op: batch_op.drop_index("ix_data") - insp = inspect(config.db) + insp = inspect(self.conn) eq_(insp.get_indexes("foo"), []) @@ -2316,7 +2346,7 @@ ) as batch_op: batch_op.add_column(Column("data", Integer)) - insp = inspect(config.db) + insp = inspect(self.conn) eq_( [ diff -Nru alembic-1.7.1/tests/test_command.py alembic-1.7.6/tests/test_command.py --- alembic-1.7.1/tests/test_command.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_command.py 2022-02-01 15:00:09.000000000 +0000 @@ -5,9 +5,11 @@ from io import TextIOWrapper import os import re +from typing import cast from sqlalchemy import exc as sqla_exc from sqlalchemy import text +from sqlalchemy.engine import Engine from alembic import __version__ from alembic import command @@ -1185,3 +1187,36 @@ is_true("test_prog" in str(buf.getvalue())) is_true(__version__ in str(buf.getvalue())) + + +class EnureVersionTest(TestBase): + @classmethod + def setup_class(cls): + cls.bind = _sqlite_file_db() + cls.env = staging_env() + cls.cfg = _sqlite_testing_config() + + @classmethod + def teardown_class(cls): + clear_staging_env() + + def test_ensure_version(self): + command.ensure_version(self.cfg) + + engine = cast(Engine, self.bind) + with engine.connect() as conn: + is_true(_connectable_has_table(conn, "alembic_version", None)) + + def test_ensure_version_called_twice(self): + command.ensure_version(self.cfg) + command.ensure_version(self.cfg) + + engine = cast(Engine, self.bind) + with engine.connect() as conn: + is_true(_connectable_has_table(conn, "alembic_version", None)) + + def test_sql_ensure_version(self): + with capture_context_buffer() as buf: + command.ensure_version(self.cfg, sql=True) + + is_true(buf.getvalue().startswith("CREATE TABLE alembic_version")) diff -Nru alembic-1.7.1/tests/test_postgresql.py alembic-1.7.6/tests/test_postgresql.py --- alembic-1.7.1/tests/test_postgresql.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_postgresql.py 2022-02-01 15:00:09.000000000 +0000 @@ -48,6 +48,7 @@ from alembic.testing.env import staging_env from alembic.testing.env import write_script from alembic.testing.fixtures import capture_context_buffer +from alembic.testing.fixtures import FutureEngineMixin from alembic.testing.fixtures import op_fixture from alembic.testing.fixtures import TablesTest from alembic.testing.fixtures import TestBase @@ -457,6 +458,10 @@ ) +class PGAutocommitBlockTestFuture(FutureEngineMixin, PGAutocommitBlockTest): + pass + + class PGOfflineEnumTest(TestBase): def setUp(self): staging_env() diff -Nru alembic-1.7.1/tests/test_post_write.py alembic-1.7.6/tests/test_post_write.py --- alembic-1.7.1/tests/test_post_write.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_post_write.py 2022-02-01 15:00:09.000000000 +0000 @@ -134,12 +134,20 @@ ): self.cfg = _no_sql_testing_config(directives=input_config) - class MocksCantName: - name = "black" - attr = "bar" - module = "black_module.foo" + retVal = [ + compat.EntryPoint( + name="black", + value="black.foo:patched_main", + group="console_scripts", + ), + compat.EntryPoint( + name="alembic", + value="alembic.config:main", + group="console_scripts", + ), + ] - importlib_metadata_get = mock.Mock(return_value=iter([MocksCantName])) + importlib_metadata_get = mock.Mock(return_value=retVal) with mock.patch( "alembic.util.compat.importlib_metadata_get", importlib_metadata_get, @@ -157,7 +165,7 @@ [ sys.executable, "-c", - "import black_module.foo; black_module.foo.bar()", + "import black.foo; black.foo.patched_main()", ] + expected_additional_arguments_fn(rev.path), cwd=cwd, diff -Nru alembic-1.7.1/tests/test_script_consumption.py alembic-1.7.6/tests/test_script_consumption.py --- alembic-1.7.1/tests/test_script_consumption.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_script_consumption.py 2022-02-01 15:00:09.000000000 +0000 @@ -28,7 +28,6 @@ from alembic.testing.fixtures import capture_context_buffer from alembic.testing.fixtures import FutureEngineMixin from alembic.testing.fixtures import TestBase -from alembic.util import compat class PatchEnvironment: @@ -117,14 +116,20 @@ @testing.combinations( - (False, True, False), - (True, False, False), - (True, True, False), - (False, True, True), - (True, False, True), - (True, True, True), - argnames="transactional_ddl,transaction_per_migration,branched_connection", - id_="rrr", + ( + False, + True, + ), + ( + True, + False, + ), + ( + True, + True, + ), + argnames="transactional_ddl,transaction_per_migration", + id_="rr", ) class ApplyVersionsFunctionalTest(PatchEnvironment, TestBase): __only_on__ = "sqlite" @@ -277,6 +282,11 @@ assert not db.dialect.has_table(db.connect(), "bat") +class LegacyApplyVersionsFunctionalTest(ApplyVersionsFunctionalTest): + __requires__ = ("sqlalchemy_1x",) + branched_connection = True + + # class level combinations can't do the skips for SQLAlchemy 1.3 # so we have a separate class @testing.combinations( @@ -372,7 +382,7 @@ assert isinstance(step.is_upgrade, bool) assert isinstance(step.is_stamp, bool) assert isinstance(step.is_migration, bool) - assert isinstance(step.up_revision_id, compat.string_types) + assert isinstance(step.up_revision_id, str) assert isinstance(step.up_revision, Script) for revtype in "up", "down", "source", "destination": @@ -382,12 +392,12 @@ assert isinstance(rev, Script) revids = getattr(step, "%s_revision_ids" % revtype) for revid in revids: - assert isinstance(revid, compat.string_types) + assert isinstance(revid, str) heads = kw["heads"] assert hasattr(heads, "__iter__") for h in heads: - assert h is None or isinstance(h, compat.string_types) + assert h is None or isinstance(h, str) class OfflineTransactionalDDLTest(TestBase): @@ -532,7 +542,7 @@ if self.is_sqlalchemy_future: with testing.expect_raises_message( sa.exc.InvalidRequestError, - r"a transaction is already begun for this connection", + r".*already", ): command.upgrade(self.cfg, c) else: @@ -554,7 +564,7 @@ if self.is_sqlalchemy_future: with testing.expect_raises_message( sa.exc.InvalidRequestError, - r"a transaction is already begun for this connection", + r".*already", ): command.upgrade(self.cfg, c) else: @@ -576,7 +586,7 @@ if self.is_sqlalchemy_future: with testing.expect_raises_message( sa.exc.InvalidRequestError, - r"a transaction is already begun for this connection", + r".*already", ): command.upgrade(self.cfg, c) else: @@ -621,6 +631,7 @@ class BranchedOnlineTransactionalDDLTest(OnlineTransactionalDDLTest): + __requires__ = ("sqlalchemy_1x",) branched_connection = True diff -Nru alembic-1.7.1/tests/test_stubs.py alembic-1.7.6/tests/test_stubs.py --- alembic-1.7.1/tests/test_stubs.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tests/test_stubs.py 2022-02-01 15:00:09.000000000 +0000 @@ -1,3 +1,4 @@ +import difflib from pathlib import Path import subprocess import sys @@ -31,11 +32,23 @@ def test_op_pyi(self): res = run_command("op") generated = res.stdout - expected = Path(alembic.__file__).parent / "op.pyi" - eq_(generated, expected.read_text()) + file_path = Path(alembic.__file__).parent / "op.pyi" + expected = file_path.read_text() + eq_(generated, expected, compare(generated, expected)) def test_context_pyi(self): res = run_command("context") generated = res.stdout - expected = Path(alembic.__file__).parent / "context.pyi" - eq_(generated, expected.read_text()) + file_path = Path(alembic.__file__).parent / "context.pyi" + expected = file_path.read_text() + eq_(generated, expected, compare(generated, expected)) + + +def compare(actual: str, expected: str): + diff = difflib.unified_diff( + actual.splitlines(), + expected.splitlines(), + fromfile="generated", + tofile="expected", + ) + return "\n".join(diff) diff -Nru alembic-1.7.1/tools/write_pyi.py alembic-1.7.6/tools/write_pyi.py --- alembic-1.7.1/tools/write_pyi.py 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tools/write_pyi.py 2022-02-01 15:00:09.000000000 +0000 @@ -9,7 +9,7 @@ sys.path.append(str(Path(__file__).parent.parent)) -if True: # avoid flake/zimports missing with the order +if True: # avoid flake/zimports messing with the order from alembic.operations.base import Operations from alembic.runtime.environment import EnvironmentContext from alembic.script.write_hooks import console_scripts @@ -19,11 +19,9 @@ IGNORE_ITEMS = { "op": {"context", "create_module_class_proxy"}, "context": { - "config", "create_module_class_proxy", "get_impl", "requires_connection", - "script", }, } @@ -36,6 +34,13 @@ ignore_output: bool, ignore_items: set, ): + if sys.version_info < (3, 9): + raise RuntimeError("This script must be run with Python 3.9 or higher") + + # When using an absolute path on windows, this will generate the correct + # relative path that shall be written to the top comment of the pyi file. + if Path(progname).is_absolute(): + progname = Path(progname).relative_to(Path().cwd()).as_posix() imports = [] read_imports = False @@ -88,7 +93,8 @@ def _generate_stub_for_attr(cls, name, printer): - printer.writeline(f"{name}: Any") + type_ = cls.__annotations__.get(name, "Any") + printer.writeline(f"{name}: {type_}") def _generate_stub_for_meth(cls, name, printer): @@ -154,15 +160,15 @@ else: with NamedTemporaryFile(delete=False, suffix=".pyi") as f: f.close() + f_path = Path(f.name) generate_pyi_for_proxy( cls_to_generate, progname, source_path=source_path, - destination_path=f.name, + destination_path=f_path, ignore_output=True, ignore_items=ignore_items, ) - f_path = Path(f.name) sys.stdout.write(f_path.read_text()) f_path.unlink() diff -Nru alembic-1.7.1/tox.ini alembic-1.7.6/tox.ini --- alembic-1.7.1/tox.ini 2021-08-30 21:49:50.000000000 +0000 +++ alembic-1.7.6/tox.ini 2022-02-01 15:00:09.000000000 +0000 @@ -10,8 +10,10 @@ deps=pytest>4.6 pytest-xdist + sqla13: pytest<7 sqla13: {[tox]SQLA_REPO}@rel_1_3#egg=sqlalchemy - sqlamaster: {[tox]SQLA_REPO}@master#egg=sqlalchemy + sqla14: {[tox]SQLA_REPO}@rel_1_4#egg=sqlalchemy + sqlamain: {[tox]SQLA_REPO}#egg=sqlalchemy postgresql: psycopg2>=2.7 mysql: mysqlclient>=1.4.0 mysql: pymysql @@ -57,7 +59,7 @@ {oracle,mssql}: python reap_dbs.py db_idents.txt -[testenv:mypy] +[testenv:pep484] basepython = python3 deps= mypy @@ -66,10 +68,16 @@ mako types-pkg-resources types-python-dateutil - # is imported in alembic/testing and mypy complains if it's installed. + # is imported in alembic/testing and mypy complains if it's not installed. pytest commands = mypy ./alembic/ --exclude alembic/templates +[testenv:mypy] +basepython = {[testenv:pep484]basepython} +deps= + {[testenv:pep484]deps} +commands = {[testenv:pep484]commands} + [testenv:pep8] basepython = python3 deps=