]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Drop compatibility with python 2.7.
authorCaselIT <cfederico87@gmail.com>
Sun, 18 Apr 2021 09:41:04 +0000 (11:41 +0200)
committerCaselIT <cfederico87@gmail.com>
Tue, 22 Jun 2021 21:05:35 +0000 (23:05 +0200)
Now alembic supports only python from version 3.6.

Change-Id: Iccf124c2d74af801d90a16c9003cdad318768625

52 files changed:
README.unittests.rst
alembic/__init__.py
alembic/autogenerate/__init__.py
alembic/autogenerate/api.py
alembic/autogenerate/compare.py
alembic/autogenerate/render.py
alembic/autogenerate/rewriter.py
alembic/config.py
alembic/ddl/__init__.py
alembic/ddl/impl.py
alembic/ddl/postgresql.py
alembic/operations/__init__.py
alembic/operations/base.py
alembic/operations/batch.py
alembic/operations/ops.py
alembic/operations/schemaobj.py
alembic/runtime/migration.py
alembic/script/__init__.py
alembic/script/base.py
alembic/script/revision.py
alembic/script/write_hooks.py
alembic/testing/__init__.py
alembic/testing/assertions.py
alembic/testing/env.py
alembic/testing/fixtures.py
alembic/testing/requirements.py
alembic/testing/schemacompare.py
alembic/testing/suite/_autogen_fixtures.py
alembic/testing/suite/test_autogen_comments.py
alembic/testing/suite/test_autogen_diffs.py
alembic/testing/suite/test_autogen_fks.py
alembic/testing/suite/test_environment.py
alembic/util/__init__.py
alembic/util/compat.py
alembic/util/editor.py
alembic/util/langhelpers.py
alembic/util/messaging.py
alembic/util/pyfiles.py
docs/build/cookbook.rst
docs/build/front.rst
docs/build/unreleased/py3.rst [new file with mode: 0644]
setup.cfg
tests/test_autogen_diffs.py
tests/test_autogen_indexes.py
tests/test_autogen_render.py
tests/test_command.py
tests/test_config.py
tests/test_editor.py
tests/test_postgresql.py
tests/test_script_consumption.py
tests/test_sqlite.py
tox.ini

index 80c1b6a21a514fceac65392404cfcfc1c1d365ab..b741d581951294b3fdca6bdb1e3fcfa6c16ca793 100644 (file)
@@ -23,20 +23,20 @@ Advanced Tox Options
 
 For more elaborate CI-style test running, the tox script provided will
 run against various Python / database targets.   For a basic run against
-Python 3.8 using an in-memory SQLite database::
+Python 3.9 using an in-memory SQLite database::
 
-    tox -e py38-sqlite
+    tox -e py39-sqlite
 
 The tox runner contains a series of target combinations that can run
 against various combinations of databases.  The test suite can be
 run against SQLite with "backend" tests also running against a PostgreSQL
 database::
 
-    tox -e py38-sqlite-postgresql
+    tox -e py39-sqlite-postgresql
 
 Or to run just "backend" tests against a MySQL database::
 
-    tox -e py38-mysql-backendonly
+    tox -e py39-mysql-backendonly
 
 Running against backends other than SQLite requires that a database of that
 vendor be available at a specific URL.  See "Setting Up Databases" below
@@ -131,7 +131,7 @@ with the tox runner also::
     [db]
     postgresql=postgresql://username:pass@hostname/dbname
 
-Now when we run ``tox -e py27-postgresql``, it will use our custom URL instead
+Now when we run ``tox -e py39-postgresql``, it will use our custom URL instead
 of the fixed one in setup.cfg.
 
 Database Configuration
index 4eb2d279d26d92508887f1728e42b15cf09cd59a..0820de065abdfc011f422320ad0cbbb98a445365 100644 (file)
@@ -1,11 +1,11 @@
 import sys
 
-from . import context  # noqa
-from . import op  # noqa
+from . import context
+from . import op
 from .runtime import environment
 from .runtime import migration
 
-__version__ = "1.6.6"
+__version__ = "1.7.0"
 
 sys.modules["alembic.migration"] = migration
 sys.modules["alembic.environment"] = environment
index a0f8ec2320f65fbf0df5caf54d43c1bc5ba9ee03..cd2ed1c15e1afc37b335d8e1f262463d600053e6 100644 (file)
@@ -1,10 +1,10 @@
-from .api import _render_migration_diffs  # noqa
-from .api import compare_metadata  # noqa
-from .api import produce_migrations  # noqa
-from .api import render_python_code  # noqa
-from .api import RevisionContext  # noqa
-from .compare import _produce_net_changes  # noqa
-from .compare import comparators  # noqa
-from .render import render_op_text  # noqa
-from .render import renderers  # noqa
-from .rewriter import Rewriter  # noqa
+from .api import _render_migration_diffs
+from .api import compare_metadata
+from .api import produce_migrations
+from .api import render_python_code
+from .api import RevisionContext
+from .compare import _produce_net_changes
+from .compare import comparators
+from .render import render_op_text
+from .render import renderers
+from .rewriter import Rewriter
index bdcfebd69bf20c6f557274f197c2f6f4a9f62432..4c156c4ee90126dd66bb0afe1ffb7a0f72245a87 100644 (file)
@@ -192,7 +192,7 @@ def _render_migration_diffs(context, template_args):
     )
 
 
-class AutogenContext(object):
+class AutogenContext:
     """Maintains configuration and state that's specific to an
     autogenerate operation."""
 
@@ -408,7 +408,7 @@ class AutogenContext(object):
         return result
 
 
-class RevisionContext(object):
+class RevisionContext:
     """Maintains configuration and state that's specific to a revision
     file generation operation."""
 
index dd71515ee894efdf44bb4fc9a1ad571a2c69298a..dbb0706c464049acee7216e25207a7f7e81cd437 100644 (file)
@@ -12,7 +12,6 @@ from alembic.ddl.base import _fk_spec
 from .render import _user_defined_render
 from .. import util
 from ..operations import ops
-from ..util import compat
 from ..util import sqla_compat
 
 log = logging.getLogger(__name__)
@@ -357,7 +356,7 @@ def _compare_columns(
             log.info("Detected removed column '%s.%s'", name, cname)
 
 
-class _constraint_sig(object):
+class _constraint_sig:
     def md_name_to_sql_name(self, context):
         return sqla_compat._get_constraint_final_name(
             self.const, context.dialect
@@ -945,7 +944,7 @@ def _render_server_default_for_compare(
         return rendered
 
     if isinstance(metadata_default, sa_schema.DefaultClause):
-        if isinstance(metadata_default.arg, compat.string_types):
+        if isinstance(metadata_default.arg, str):
             metadata_default = metadata_default.arg
         else:
             metadata_default = str(
@@ -954,7 +953,7 @@ def _render_server_default_for_compare(
                     compile_kwargs={"literal_binds": True},
                 )
             )
-    if isinstance(metadata_default, compat.string_types):
+    if isinstance(metadata_default, str):
         if metadata_col.type._type_affinity is sqltypes.String:
             metadata_default = re.sub(r"^'|'$", "", metadata_default)
             return repr(metadata_default)
index 79d02615ac18a06729975855026d886a1fc46ead..490d65cb6d9d8e91aceb7205273e9b51afe37a3e 100644 (file)
@@ -1,4 +1,5 @@
 from collections import OrderedDict
+from io import StringIO
 import re
 
 from mako.pygen import PythonPrinter
@@ -11,7 +12,6 @@ from ..operations import ops
 from ..util import compat
 from ..util import sqla_compat
 from ..util.compat import string_types
-from ..util.compat import StringIO
 
 
 MAX_PYTHON_ARGS = 255
@@ -480,7 +480,7 @@ def _alter_column(autogen_context, op):
     return text
 
 
-class _f_name(object):
+class _f_name:
     def __init__(self, prefix, name):
         self.prefix = prefix
         self.name = name
@@ -500,17 +500,7 @@ def _ident(name):
     if name is None:
         return name
     elif isinstance(name, sql.elements.quoted_name):
-        if compat.py2k:
-            # the attempt to encode to ascii here isn't super ideal,
-            # however we are trying to cut down on an explosion of
-            # u'' literals only when py2k + SQLA 0.9, in particular
-            # makes unit tests testing code generation very difficult
-            try:
-                return name.encode("ascii")
-            except UnicodeError:
-                return compat.text_type(name)
-        else:
-            return compat.text_type(name)
+        return compat.text_type(name)
     elif isinstance(name, compat.string_types):
         return name
 
index fe8bfefef2c3b5c64f04de23038889c339aba3be..ba9a06dd54829fa01419175b0be00db19ca8b424 100644 (file)
@@ -2,7 +2,7 @@ from alembic import util
 from alembic.operations import ops
 
 
-class Rewriter(object):
+class Rewriter:
     """A helper object that allows easy 'rewriting' of ops streams.
 
     The :class:`.Rewriter` object is intended to be passed along
index 565bb581fdd5c1114f9f8f854a5ba85c4ef05382..b8b465d1283bf897c8766f1947482de0fd5f6929 100644 (file)
@@ -1,4 +1,5 @@
 from argparse import ArgumentParser
+from configparser import ConfigParser
 import inspect
 import os
 import sys
@@ -7,10 +8,9 @@ from . import __version__
 from . import command
 from . import util
 from .util import compat
-from .util.compat import SafeConfigParser
 
 
-class Config(object):
+class Config:
 
     r"""Represent an Alembic configuration.
 
@@ -72,7 +72,7 @@ class Config(object):
      is **copied** to a new one, stored locally as the attribute
      ``.config_args``. When the :attr:`.Config.file_config` attribute is
      first invoked, the replacement variable ``here`` will be added to this
-     dictionary before the dictionary is passed to ``SafeConfigParser()``
+     dictionary before the dictionary is passed to ``ConfigParser()``
      to parse the .ini file.
 
     :param attributes: optional dictionary of arbitrary Python keys/values,
@@ -184,7 +184,7 @@ class Config(object):
         else:
             here = ""
         self.config_args["here"] = here
-        file_config = SafeConfigParser(self.config_args)
+        file_config = ConfigParser(self.config_args)
         if self.config_file_name:
             file_config.read([self.config_file_name])
         else:
@@ -280,7 +280,7 @@ class Config(object):
         return self.get_section_option(self.config_ini_section, name, default)
 
 
-class CommandLine(object):
+class CommandLine:
     def __init__(self, prog=None):
         self._generate_args(prog)
 
index 7d50ba041274b427c2130a8c04623ed20443f098..cfcc47e029515838acc6b972950f8bfd8454c770 100644 (file)
@@ -1,6 +1,6 @@
-from . import mssql  # noqa
-from . import mysql  # noqa
-from . import oracle  # noqa
-from . import postgresql  # noqa
-from . import sqlite  # noqa
-from .impl import DefaultImpl  # noqa
+from . import mssql
+from . import mysql
+from . import oracle
+from . import postgresql
+from . import sqlite
+from .impl import DefaultImpl
index 62692d2b4414cc1ffd012a62a5d1800cf0458092..710509c2fe0d39d5dffa5b1cba623f1291b3b19f 100644 (file)
@@ -10,7 +10,6 @@ from .. import util
 from ..util import sqla_compat
 from ..util.compat import string_types
 from ..util.compat import text_type
-from ..util.compat import with_metaclass
 
 
 class ImplMeta(type):
@@ -26,7 +25,7 @@ _impls = {}
 Params = namedtuple("Params", ["token0", "tokens", "args", "kwargs"])
 
 
-class DefaultImpl(with_metaclass(ImplMeta)):
+class DefaultImpl(metaclass=ImplMeta):
 
     """Provide the entrypoint for major migration operations,
     including database-specific behavioral variances.
index 1e6558605886302919c74db2ac2b7a2b8915e737..7468f082dade0bab780a757266b639f0d399a3ed 100644 (file)
@@ -76,12 +76,6 @@ class PostgresqlImpl(DefaultImpl):
         if None in (conn_col_default, rendered_metadata_default):
             return not defaults_equal
 
-        if compat.py2k:
-            # look for a python 2 "u''" string and filter
-            m = re.match(r"^u'(.*)'$", rendered_metadata_default)
-            if m:
-                rendered_metadata_default = "'%s'" % m.group(1)
-
         # check for unquoted string and quote for PG String types
         if (
             not isinstance(inspector_column.type, Numeric)
index dc2d3a492eeedf052dc111db70ea1936ae0b5867..9527620ded60ddc3f2aba2ab416ee9cce5e4a4e3 100644 (file)
@@ -1,4 +1,4 @@
-from . import toimpl  # noqa
+from . import toimpl
 from .base import BatchOperations
 from .base import Operations
 from .ops import MigrateOperation
index d914c8d4442d884fa7d588abd0adc82833bc3e0c..cd1408046297cb0de75afb4beba5f8c49f33b228 100644 (file)
@@ -5,7 +5,6 @@ from . import batch
 from . import schemaobj
 from .. import util
 from ..util import sqla_compat
-from ..util.compat import exec_
 from ..util.compat import inspect_formatargspec
 from ..util.compat import inspect_getargspec
 
@@ -129,7 +128,7 @@ class Operations(util.ModuleClsProxy):
             )
             globals_ = {"op_cls": op_cls}
             lcl = {}
-            exec_(func_text, globals_, lcl)
+            exec(func_text, globals_, lcl)
             setattr(cls, name, lcl[name])
             fn.__func__.__doc__ = (
                 "This method is proxied on "
index b9268603ffa0837ea3c181648eb3438d2407a823..656b8686bb9f78a887792c3af50139196c910804 100644 (file)
@@ -22,7 +22,7 @@ from ..util.sqla_compat import _remove_column_from_collection
 from ..util.sqla_compat import _select
 
 
-class BatchOperationsImpl(object):
+class BatchOperationsImpl:
     def __init__(
         self,
         operations,
@@ -165,7 +165,7 @@ class BatchOperationsImpl(object):
         self.batch.append(("create_column_comment", (column,), {}))
 
 
-class ApplyBatchImpl(object):
+class ApplyBatchImpl:
     def __init__(
         self,
         impl,
index 8d300ec9952781d0c50575e1b629af913ac4dc1f..87ad552d80ec15fdde8733887fd9d87c3ed1292e 100644 (file)
@@ -9,7 +9,7 @@ from .. import util
 from ..util import sqla_compat
 
 
-class MigrateOperation(object):
+class MigrateOperation:
     """base class for migration command and organization objects.
 
     This system is part of the operation extensibility API.
index 9572e1912a907a22541e9a1b8baa549be59e6f91..adbffdc013707b716a610549d028041dc4af8b35 100644 (file)
@@ -7,11 +7,10 @@ from sqlalchemy.types import NULLTYPE
 
 from .. import util
 from ..util import sqla_compat
-from ..util.compat import raise_
 from ..util.compat import string_types
 
 
-class SchemaObjects(object):
+class SchemaObjects:
     def __init__(self, migration_context=None):
         self.migration_context = migration_context
 
@@ -118,13 +117,10 @@ class SchemaObjects(object):
         try:
             const = types[type_]
         except KeyError as ke:
-            raise_(
-                TypeError(
-                    "'type' can be one of %s"
-                    % ", ".join(sorted(repr(x) for x in types))
-                ),
-                from_=ke,
-            )
+            raise TypeError(
+                "'type' can be one of %s"
+                % ", ".join(sorted(repr(x) for x in types))
+            ) from ke
         else:
             const = const(name=name)
             t.append_constraint(const)
index bf48e2bd9816feccb152ab3366ed186972e710d8..5ed2136da366e1fe26b29a97f74a0c3659649dba 100644 (file)
@@ -15,13 +15,12 @@ from sqlalchemy.engine.strategies import MockEngineStrategy
 from .. import ddl
 from .. import util
 from ..util import sqla_compat
-from ..util.compat import callable
 from ..util.compat import EncodedIO
 
 log = logging.getLogger(__name__)
 
 
-class _ProxyTransaction(object):
+class _ProxyTransaction:
     def __init__(self, migration_context):
         self.migration_context = migration_context
 
@@ -46,7 +45,7 @@ class _ProxyTransaction(object):
             self.migration_context._transaction = None
 
 
-class MigrationContext(object):
+class MigrationContext:
 
     """Represent the database state made available to a migration
     script.
@@ -681,7 +680,7 @@ class MigrationContext(object):
         )
 
 
-class HeadMaintainer(object):
+class HeadMaintainer:
     def __init__(self, context, heads):
         self.context = context
         self.heads = set(heads)
@@ -788,7 +787,7 @@ class HeadMaintainer(object):
             self._update_version(from_, to_)
 
 
-class MigrationInfo(object):
+class MigrationInfo:
     """Exposes information about a migration step to a callback listener.
 
     The :class:`.MigrationInfo` object is available exclusively for the
@@ -914,7 +913,7 @@ class MigrationInfo(object):
         return self.revision_map.get_revisions(self.destination_revision_ids)
 
 
-class MigrationStep(object):
+class MigrationStep:
     @property
     def name(self):
         return self.migration_fn.__name__
index 540d62729067e2a0b113c416045a91aa7b84b655..d78f3f1dc54c13a52b64e8d668c2baf708eb20bc 100644 (file)
@@ -1,4 +1,4 @@
-from .base import Script  # noqa
-from .base import ScriptDirectory  # noqa
+from .base import Script
+from .base import ScriptDirectory
 
 __all__ = ["ScriptDirectory", "Script"]
index 723c01161445dde51203b313239533aaf3f82fdb..13e88c89f240a3088527102455fbf307bcdb827e 100644 (file)
@@ -9,7 +9,6 @@ from . import revision
 from . import write_hooks
 from .. import util
 from ..runtime import migration
-from ..util import compat
 
 try:
     from dateutil import tz
@@ -27,7 +26,7 @@ _split_on_space_comma = re.compile(r", *|(?: +)")
 _split_on_space_comma_colon = re.compile(r", *|(?: +)|\:")
 
 
-class ScriptDirectory(object):
+class ScriptDirectory:
 
     """Provides operations upon an Alembic script directory.
 
@@ -183,7 +182,7 @@ class ScriptDirectory(object):
                     "ancestor/descendant revisions along the same branch"
                 )
             ancestor = ancestor % {"start": start, "end": end}
-            compat.raise_(util.CommandError(ancestor), from_=rna)
+            raise util.CommandError(ancestor) from rna
         except revision.MultipleHeads as mh:
             if not multiple_heads:
                 multiple_heads = (
@@ -197,15 +196,15 @@ class ScriptDirectory(object):
                 "head_arg": end or mh.argument,
                 "heads": util.format_as_comma(mh.heads),
             }
-            compat.raise_(util.CommandError(multiple_heads), from_=mh)
+            raise util.CommandError(multiple_heads) from mh
         except revision.ResolutionError as re:
             if resolution is None:
                 resolution = "Can't locate revision identified by '%s'" % (
                     re.argument
                 )
-            compat.raise_(util.CommandError(resolution), from_=re)
+            raise util.CommandError(resolution) from re
         except revision.RevisionError as err:
-            compat.raise_(util.CommandError(err.args[0]), from_=err)
+            raise util.CommandError(err.args[0]) from err
 
     def walk_revisions(self, base="base", head="heads"):
         """Iterate through all revisions.
@@ -577,7 +576,7 @@ class ScriptDirectory(object):
         try:
             Script.verify_rev_id(revid)
         except revision.RevisionError as err:
-            compat.raise_(util.CommandError(err.args[0]), from_=err)
+            raise util.CommandError(err.args[0]) from err
 
         with self._catch_revision_errors(
             multiple_heads=(
@@ -665,7 +664,7 @@ class ScriptDirectory(object):
         try:
             script = Script._from_path(self, path)
         except revision.RevisionError as err:
-            compat.raise_(util.CommandError(err.args[0]), from_=err)
+            raise util.CommandError(err.args[0]) from err
         if branch_labels and not script.branch_labels:
             raise util.CommandError(
                 "Version %s specified branch_labels %s, however the "
index f2b4f7069ecc64fd1bcda6d403a87672fe0e76b8..bdae805db03e72d03e0864d6ba509ddba67d24b8 100644 (file)
@@ -72,7 +72,7 @@ class DependencyLoopDetected(DependencyCycleDetected, LoopDetected):
         super(DependencyLoopDetected, self).__init__(revision)
 
 
-class RevisionMap(object):
+class RevisionMap:
     """Maintains a map of :class:`.Revision` objects.
 
     :class:`.RevisionMap` is used by :class:`.ScriptDirectory` to maintain
@@ -510,12 +510,10 @@ class RevisionMap(object):
             try:
                 nonbranch_rev = self._revision_for_ident(branch_label)
             except ResolutionError as re:
-                util.raise_(
-                    ResolutionError(
-                        "No such branch: '%s'" % branch_label, branch_label
-                    ),
-                    from_=re,
-                )
+                raise ResolutionError(
+                    "No such branch: '%s'" % branch_label, branch_label
+                ) from re
+
             else:
                 return nonbranch_rev
         else:
@@ -1047,7 +1045,7 @@ class RevisionMap(object):
         # No relative destination given, revision specified is absolute.
         branch_label, _, symbol = target.rpartition("@")
         if not branch_label:
-            branch_label is None
+            branch_label = None
         return branch_label, self.get_revision(symbol)
 
     def _parse_upgrade_target(
@@ -1337,7 +1335,7 @@ class RevisionMap(object):
         return needs, targets
 
 
-class Revision(object):
+class Revision:
     """Base class for revisioned objects.
 
     The :class:`.Revision` class is the base of the more public-facing
index c69ce5a42f31fef7d7ab6120d7b66ec1ad1fb916..8cd3dccc20f749e51c56a7a945cdde3aec00fb90 100644 (file)
@@ -43,10 +43,9 @@ def _invoke(name, revision, options):
     try:
         hook = _registry[name]
     except KeyError as ke:
-        compat.raise_(
-            util.CommandError("No formatter with name '%s' registered" % name),
-            from_=ke,
-        )
+        raise util.CommandError(
+            "No formatter with name '%s' registered" % name
+        ) from ke
     else:
         return hook(revision, options)
 
@@ -70,13 +69,9 @@ def _run_hooks(path, hook_config):
         try:
             type_ = opts["type"]
         except KeyError as ke:
-            compat.raise_(
-                util.CommandError(
-                    "Key %s.type is required for post write hook %r"
-                    % (name, name)
-                ),
-                from_=ke,
-            )
+            raise util.CommandError(
+                "Key %s.type is required for post write hook %r" % (name, name)
+            ) from ke
         else:
             util.status(
                 'Running post write hook "%s"' % name,
@@ -116,13 +111,10 @@ def console_scripts(path, options):
     try:
         entrypoint_name = options["entrypoint"]
     except KeyError as ke:
-        compat.raise_(
-            util.CommandError(
-                "Key %s.entrypoint is required for post write hook %r"
-                % (options["_hook_name"], options["_hook_name"])
-            ),
-            from_=ke,
-        )
+        raise util.CommandError(
+            "Key %s.entrypoint is required for post write hook %r"
+            % (options["_hook_name"], options["_hook_name"])
+        ) from ke
     iter_ = pkg_resources.iter_entry_points("console_scripts", entrypoint_name)
     impl = next(iter_)
     cwd = options.get("cwd", None)
index 50f1f4b145ce5c854159463d217e611b134a4a04..05b2e340eacaf2cc4bacc1ca5bf38ec35b92791f 100644 (file)
@@ -1,32 +1,31 @@
-from sqlalchemy.testing import config  # noqa
-from sqlalchemy.testing import emits_warning  # noqa
-from sqlalchemy.testing import engines  # noqa
-from sqlalchemy.testing import exclusions  # noqa
-from sqlalchemy.testing import mock  # noqa
-from sqlalchemy.testing import provide_metadata  # noqa
-from sqlalchemy.testing import uses_deprecated  # noqa
-from sqlalchemy.testing.config import combinations  # noqa
-from sqlalchemy.testing.config import fixture  # noqa
-from sqlalchemy.testing.config import requirements as requires  # noqa
+from sqlalchemy.testing import config
+from sqlalchemy.testing import emits_warning
+from sqlalchemy.testing import engines
+from sqlalchemy.testing import exclusions
+from sqlalchemy.testing import mock
+from sqlalchemy.testing import provide_metadata
+from sqlalchemy.testing import uses_deprecated
+from sqlalchemy.testing.config import combinations
+from sqlalchemy.testing.config import fixture
+from sqlalchemy.testing.config import requirements as requires
 
-from alembic import util  # noqa
-from .assertions import assert_raises  # noqa
-from .assertions import assert_raises_message  # noqa
-from .assertions import emits_python_deprecation_warning  # noqa
-from .assertions import eq_  # noqa
-from .assertions import eq_ignore_whitespace  # noqa
-from .assertions import expect_raises  # noqa
-from .assertions import expect_raises_message  # noqa
-from .assertions import expect_sqlalchemy_deprecated  # noqa
-from .assertions import expect_sqlalchemy_deprecated_20  # noqa
-from .assertions import expect_warnings  # noqa
-from .assertions import is_  # noqa
-from .assertions import is_false  # noqa
-from .assertions import is_not_  # noqa
-from .assertions import is_true  # noqa
-from .assertions import ne_  # noqa
-from .fixtures import TestBase  # noqa
-from .util import resolve_lambda  # noqa
+from .assertions import assert_raises
+from .assertions import assert_raises_message
+from .assertions import emits_python_deprecation_warning
+from .assertions import eq_
+from .assertions import eq_ignore_whitespace
+from .assertions import expect_raises
+from .assertions import expect_raises_message
+from .assertions import expect_sqlalchemy_deprecated
+from .assertions import expect_sqlalchemy_deprecated_20
+from .assertions import expect_warnings
+from .assertions import is_
+from .assertions import is_false
+from .assertions import is_not_
+from .assertions import is_true
+from .assertions import ne_
+from .fixtures import TestBase
+from .util import resolve_lambda
 
 try:
     from sqlalchemy.testing import asyncio
index 6d39f4c41ee37382775ae4391bc5242874949b16..e22ac6b6727401738fc47bd4c70f15ba37d80dcf 100644 (file)
@@ -17,7 +17,6 @@ from sqlalchemy.testing.assertions import ne_  # noqa
 from sqlalchemy.util import decorator
 
 from ..util import sqla_compat
-from ..util.compat import py3k
 
 
 def _assert_proper_exception_context(exception):
@@ -30,9 +29,6 @@ def _assert_proper_exception_context(exception):
 
     """
 
-    if not util.py3k:
-        return
-
     if (
         exception.__context__ is not exception.__cause__
         and not exception.__suppress_context__
@@ -73,7 +69,7 @@ def _assert_raises(
     return ec.error
 
 
-class _ErrorContainer(object):
+class _ErrorContainer:
     error = None
 
 
@@ -109,21 +105,12 @@ def expect_raises_message(except_cls, msg, check_context=True):
 
 
 def eq_ignore_whitespace(a, b, msg=None):
-    # sqlalchemy.testing.assertion has this function
-    # but not with the special "!U" detection part
 
     a = re.sub(r"^\s+?|\n", "", a)
     a = re.sub(r" {2,}", " ", a)
     b = re.sub(r"^\s+?|\n", "", b)
     b = re.sub(r" {2,}", " ", b)
 
-    # convert for unicode string rendering,
-    # using special escape character "!U"
-    if py3k:
-        b = re.sub(r"!U", "", b)
-    else:
-        b = re.sub(r"!U", "u", b)
-
     assert a == b, msg or "%r != %r" % (a, b)
 
 
index 62b74ec577f031f8f7e39a96922d58c72d5c2ba7..2947085428396bb568a01d77cda8ba3d73790dd5 100644 (file)
@@ -1,5 +1,5 @@
 #!coding: utf-8
-
+import importlib.machinery
 import os
 import shutil
 import textwrap
@@ -11,9 +11,6 @@ from . import util as testing_util
 from .. import util
 from ..script import Script
 from ..script import ScriptDirectory
-from ..util.compat import get_current_bytecode_suffixes
-from ..util.compat import has_pep3147
-from ..util.compat import u
 
 
 def _get_staging_directory():
@@ -290,15 +287,13 @@ def make_sourceless(path, style):
 
     py_compile.compile(path)
 
-    if style == "simple" and has_pep3147():
+    if style == "simple":
         pyc_path = util.pyc_file_from_path(path)
-        suffix = get_current_bytecode_suffixes()[0]
+        suffix = importlib.machinery.BYTECODE_SUFFIXES[0]
         filepath, ext = os.path.splitext(path)
         simple_pyc_path = filepath + suffix
         shutil.move(pyc_path, simple_pyc_path)
         pyc_path = simple_pyc_path
-    elif style == "pep3147" and not has_pep3147():
-        raise NotImplementedError()
     else:
         assert style in ("pep3147", "simple")
         pyc_path = util.pyc_file_from_path(path)
@@ -341,11 +336,10 @@ def downgrade():
     write_script(
         script,
         b,
-        u(
-            """# coding: utf-8
+        f"""# coding: utf-8
 "Rev B, méil, %3"
-revision = '{}'
-down_revision = '{}'
+revision = '{b}'
+down_revision = '{a}'
 
 from alembic import op
 
@@ -357,8 +351,7 @@ def upgrade():
 def downgrade():
     op.execute("DROP STEP 2")
 
-"""
-        ).format(b, a),
+""",
         encoding="utf-8",
     )
 
index d5d45ac73bfb4d183027a804487bab06af6235f5..cccc3822623209fbbf91bd84c09b15336bb4e433 100644 (file)
@@ -1,4 +1,5 @@
 # coding: utf-8
+import configparser
 from contextlib import contextmanager
 import io
 import re
@@ -21,9 +22,7 @@ from .assertions import _get_dialect
 from ..environment import EnvironmentContext
 from ..migration import MigrationContext
 from ..operations import Operations
-from ..util import compat
 from ..util import sqla_compat
-from ..util.compat import configparser
 from ..util.compat import string_types
 from ..util.compat import text_type
 from ..util.sqla_compat import create_mock_engine
@@ -62,7 +61,7 @@ if sqla_14:
     from sqlalchemy.testing.fixtures import FutureEngineMixin
 else:
 
-    class FutureEngineMixin(object):
+    class FutureEngineMixin:
         __requires__ = ("sqlalchemy_14",)
 
 
@@ -105,7 +104,7 @@ def capture_engine_context_buffer(**kw):
     from .env import _sqlite_file_db
     from sqlalchemy import event
 
-    buf = compat.StringIO()
+    buf = io.StringIO()
 
     eng = _sqlite_file_db()
 
@@ -138,7 +137,7 @@ def op_fixture(
     if naming_convention:
         opts["target_metadata"] = MetaData(naming_convention=naming_convention)
 
-    class buffer_(object):
+    class buffer_:
         def __init__(self):
             self.lines = []
 
@@ -215,7 +214,7 @@ def op_fixture(
     return context
 
 
-class AlterColRoundTripFixture(object):
+class AlterColRoundTripFixture:
 
     # since these tests are about syntax, use more recent SQLAlchemy as some of
     # the type / server default compare logic might not work on older
index 92fe372839cfe119b532573e6e429950c1fde1a5..d6a62b47faf4c9cdc533987ab95afff29e55e3ee 100644 (file)
@@ -87,11 +87,6 @@ class SuiteRequirements(Requirements):
             lambda: sys.version_info < (3,), "Python version 3.xx is required."
         )
 
-    @property
-    def pep3147(self):
-
-        return exclusions.only_if(lambda config: util.compat.has_pep3147())
-
     @property
     def comments(self):
         return exclusions.only_if(
index c3a7382357e91cf759812aa7c2e5577cd3cf2b64..500cee80622cd779eaff478decc437bb882cd841 100644 (file)
@@ -2,7 +2,7 @@ from sqlalchemy import schema
 from sqlalchemy import util
 
 
-class CompareTable(object):
+class CompareTable:
     def __init__(self, table):
         self.table = table
 
@@ -26,7 +26,7 @@ class CompareTable(object):
         return not self.__eq__(other)
 
 
-class CompareColumn(object):
+class CompareColumn:
     def __init__(self, column):
         self.column = column
 
@@ -41,7 +41,7 @@ class CompareColumn(object):
         return not self.__eq__(other)
 
 
-class CompareIndex(object):
+class CompareIndex:
     def __init__(self, index):
         self.index = index
 
@@ -56,7 +56,7 @@ class CompareIndex(object):
         return not self.__eq__(other)
 
 
-class CompareCheckConstraint(object):
+class CompareCheckConstraint:
     def __init__(self, constraint):
         self.constraint = constraint
 
@@ -73,7 +73,7 @@ class CompareCheckConstraint(object):
         return not self.__eq__(other)
 
 
-class CompareForeignKey(object):
+class CompareForeignKey:
     def __init__(self, constraint):
         self.constraint = constraint
 
@@ -99,7 +99,7 @@ class CompareForeignKey(object):
         return not self.__eq__(other)
 
 
-class ComparePrimaryKey(object):
+class ComparePrimaryKey:
     def __init__(self, constraint):
         self.constraint = constraint
 
@@ -127,7 +127,7 @@ class ComparePrimaryKey(object):
         return not self.__eq__(other)
 
 
-class CompareUniqueConstraint(object):
+class CompareUniqueConstraint:
     def __init__(self, constraint):
         self.constraint = constraint
 
index 6a03a3051074e05e29a86b3721617480b9ca05e0..44fc24f334f71de0f120e15b24e3832dce214511 100644 (file)
@@ -45,7 +45,7 @@ _default_object_filters = _default_include_object
 _default_name_filters = None
 
 
-class ModelOne(object):
+class ModelOne:
     __requires__ = ("unique_constraint_reflection",)
 
     schema = None
@@ -143,7 +143,7 @@ class ModelOne(object):
         return m
 
 
-class _ComparesFKs(object):
+class _ComparesFKs:
     def _assert_fk_diff(
         self,
         diff,
index 4e8ec5a0f1df92d2599aebdfd4353469808e11bc..7ef074f57893180048c6193455b0dd1d507c0603 100644 (file)
@@ -8,9 +8,6 @@ from ._autogen_fixtures import AutogenFixtureTest
 from ...testing import eq_
 from ...testing import mock
 from ...testing import TestBase
-from ...util import compat
-
-py3k = compat.py3k
 
 
 class AutogenerateCommentsTest(AutogenFixtureTest, TestBase):
index eafd1a8bda1df2031bc571c2ef193c55f362c9be..75bcd37aeec53d4afb2447a0f7aaf8ab5ef4c160 100644 (file)
@@ -11,9 +11,6 @@ from ...testing import config
 from ...testing import eq_
 from ...testing import is_
 from ...testing import TestBase
-from ...util import compat
-
-py3k = compat.py3k
 
 
 class AlterColumnTest(AutogenFixtureTest, TestBase):
index 323beb6211a533c5ac7cbfddbaaa01638adbd69c..763f40ed224c4894ac49820297960fed2f909046 100644 (file)
@@ -11,9 +11,6 @@ from ...testing import config
 from ...testing import eq_
 from ...testing import mock
 from ...testing import TestBase
-from ...util import compat
-
-py3k = compat.py3k
 
 
 class AutogenerateForeignKeysTest(AutogenFixtureTest, TestBase):
index 2d3e97e4b08f87b696e45d2a26d7d5e3a294b2ea..a761632b33d278cfd426da09c4440a792f9529e1 100644 (file)
@@ -1,4 +1,5 @@
-#!coding: utf-8
+import io
+
 from ...migration import MigrationContext
 from ...testing import assert_raises
 from ...testing import config
@@ -6,7 +7,6 @@ from ...testing import eq_
 from ...testing import is_false
 from ...testing import is_true
 from ...testing.fixtures import TestBase
-from ...util import compat
 
 
 class MigrationTransactionTest(TestBase):
@@ -23,7 +23,7 @@ class MigrationTransactionTest(TestBase):
             )
             self.context.output_buffer = (
                 self.context.impl.output_buffer
-            ) = compat.StringIO()
+            ) = io.StringIO()
         else:
             self.context = MigrationContext.configure(
                 connection=conn, opts=opts
@@ -308,7 +308,7 @@ class MigrationTransactionTest(TestBase):
     def _assert_impl_steps(self, *steps):
         to_check = self.context.output_buffer.getvalue()
 
-        self.context.impl.output_buffer = buf = compat.StringIO()
+        self.context.impl.output_buffer = buf = io.StringIO()
         for step in steps:
             if step == "BEGIN":
                 self.context.impl.emit_begin()
index 2a3d8a88ec9607b599faf1d896341641f5cfac31..15c8f4e509b3d5d733c2335d4f89e6ad16569304 100644 (file)
@@ -1,31 +1,30 @@
-from .compat import raise_  # noqa
-from .editor import open_in_editor  # noqa
+from .editor import open_in_editor
 from .exc import CommandError
-from .langhelpers import _with_legacy_names  # noqa
-from .langhelpers import asbool  # noqa
-from .langhelpers import dedupe_tuple  # noqa
-from .langhelpers import Dispatcher  # noqa
-from .langhelpers import immutabledict  # noqa
-from .langhelpers import memoized_property  # noqa
-from .langhelpers import ModuleClsProxy  # noqa
-from .langhelpers import rev_id  # noqa
-from .langhelpers import to_list  # noqa
-from .langhelpers import to_tuple  # noqa
-from .langhelpers import unique_list  # noqa
-from .messaging import err  # noqa
-from .messaging import format_as_comma  # noqa
-from .messaging import msg  # noqa
-from .messaging import obfuscate_url_pw  # noqa
-from .messaging import status  # noqa
-from .messaging import warn  # noqa
-from .messaging import write_outstream  # noqa
-from .pyfiles import coerce_resource_to_filename  # noqa
-from .pyfiles import load_python_file  # noqa
-from .pyfiles import pyc_file_from_path  # noqa
-from .pyfiles import template_to_file  # noqa
-from .sqla_compat import has_computed  # noqa
-from .sqla_compat import sqla_13  # noqa
-from .sqla_compat import sqla_14  # noqa
+from .langhelpers import _with_legacy_names
+from .langhelpers import asbool
+from .langhelpers import dedupe_tuple
+from .langhelpers import Dispatcher
+from .langhelpers import immutabledict
+from .langhelpers import memoized_property
+from .langhelpers import ModuleClsProxy
+from .langhelpers import rev_id
+from .langhelpers import to_list
+from .langhelpers import to_tuple
+from .langhelpers import unique_list
+from .messaging import err
+from .messaging import format_as_comma
+from .messaging import msg
+from .messaging import obfuscate_url_pw
+from .messaging import status
+from .messaging import warn
+from .messaging import write_outstream
+from .pyfiles import coerce_resource_to_filename
+from .pyfiles import load_python_file
+from .pyfiles import pyc_file_from_path
+from .pyfiles import template_to_file
+from .sqla_compat import has_computed
+from .sqla_compat import sqla_13
+from .sqla_compat import sqla_14
 
 
 if not sqla_13:
index a4433d0bad68719f1effc665f1a1a4888408ecb7..0fdd86dcd02021a4de1debbbf6388c23bb2ffc4b 100644 (file)
@@ -2,14 +2,9 @@ import collections
 import inspect
 import io
 import os
-import sys
 
-py2k = sys.version_info.major < 3
-py3k = sys.version_info.major >= 3
-py36 = sys.version_info >= (3, 6)
 is_posix = os.name == "posix"
 
-
 ArgSpec = collections.namedtuple(
     "ArgSpec", ["args", "varargs", "keywords", "defaults"]
 )
@@ -29,7 +24,7 @@ def inspect_getargspec(func):
 
     nargs = co.co_argcount
     names = co.co_varnames
-    nkwargs = co.co_kwonlyargcount if py3k else 0
+    nkwargs = co.co_kwonlyargcount
     args = list(names[:nargs])
 
     nargs += nkwargs
@@ -44,272 +39,79 @@ def inspect_getargspec(func):
     return ArgSpec(args, varargs, varkw, func.__defaults__)
 
 
-if py3k:
-    from io import StringIO
-else:
-    # accepts strings
-    from StringIO import StringIO  # noqa
-
-if py3k:
-    import builtins as compat_builtins
-
-    string_types = (str,)
-    binary_type = bytes
-    text_type = str
-
-    def callable(fn):  # noqa
-        return hasattr(fn, "__call__")
-
-    def u(s):
-        return s
-
-    def ue(s):
-        return s
-
-    range = range  # noqa
-else:
-    import __builtin__ as compat_builtins
-
-    string_types = (basestring,)  # noqa
-    binary_type = str
-    text_type = unicode  # noqa
-    callable = callable  # noqa
-
-    def u(s):
-        return unicode(s, "utf-8")  # noqa
-
-    def ue(s):
-        return unicode(s, "unicode_escape")  # noqa
-
-    range = xrange  # noqa
-
-if py3k:
-    import collections.abc as collections_abc
-else:
-    import collections as collections_abc  # noqa
-
-if py3k:
-
-    def _formatannotation(annotation, base_module=None):
-        """vendored from python 3.7"""
-
-        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 annotation.__module__ + "." + annotation.__qualname__
-        return repr(annotation)
-
-    def inspect_formatargspec(
-        args,
-        varargs=None,
-        varkw=None,
-        defaults=None,
-        kwonlyargs=(),
-        kwonlydefaults={},
-        annotations={},
-        formatarg=str,
-        formatvarargs=lambda name: "*" + name,
-        formatvarkw=lambda name: "**" + name,
-        formatvalue=lambda value: "=" + repr(value),
-        formatreturns=lambda text: " -> " + text,
-        formatannotation=_formatannotation,
-    ):
-        """Copy formatargspec from python 3.7 standard library.
-
-        Python 3 has deprecated formatargspec and requested that Signature
-        be used instead, however this requires a full reimplementation
-        of formatargspec() in terms of creating Parameter objects and such.
-        Instead of introducing all the object-creation overhead and having
-        to reinvent from scratch, just copy their compatibility routine.
-
-        """
-
-        def formatargandannotation(arg):
-            result = formatarg(arg)
-            if arg in annotations:
-                result += ": " + formatannotation(annotations[arg])
-            return result
-
-        specs = []
-        if defaults:
-            firstdefault = len(args) - len(defaults)
-        for i, arg in enumerate(args):
-            spec = formatargandannotation(arg)
-            if defaults and i >= firstdefault:
-                spec = spec + formatvalue(defaults[i - firstdefault])
-            specs.append(spec)
-        if varargs is not None:
-            specs.append(formatvarargs(formatargandannotation(varargs)))
-        else:
-            if kwonlyargs:
-                specs.append("*")
-        if kwonlyargs:
-            for kwonlyarg in kwonlyargs:
-                spec = formatargandannotation(kwonlyarg)
-                if kwonlydefaults and kwonlyarg in kwonlydefaults:
-                    spec += formatvalue(kwonlydefaults[kwonlyarg])
-                specs.append(spec)
-        if varkw is not None:
-            specs.append(formatvarkw(formatargandannotation(varkw)))
-        result = "(" + ", ".join(specs) + ")"
-        if "return" in annotations:
-            result += formatreturns(formatannotation(annotations["return"]))
+string_types = (str,)
+binary_type = bytes
+text_type = str
+
+
+def _formatannotation(annotation, base_module=None):
+    """vendored from python 3.7"""
+
+    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 annotation.__module__ + "." + annotation.__qualname__
+    return repr(annotation)
+
+
+def inspect_formatargspec(
+    args,
+    varargs=None,
+    varkw=None,
+    defaults=None,
+    kwonlyargs=(),
+    kwonlydefaults={},
+    annotations={},
+    formatarg=str,
+    formatvarargs=lambda name: "*" + name,
+    formatvarkw=lambda name: "**" + name,
+    formatvalue=lambda value: "=" + repr(value),
+    formatreturns=lambda text: " -> " + text,
+    formatannotation=_formatannotation,
+):
+    """Copy formatargspec from python 3.7 standard library.
+
+    Python 3 has deprecated formatargspec and requested that Signature
+    be used instead, however this requires a full reimplementation
+    of formatargspec() in terms of creating Parameter objects and such.
+    Instead of introducing all the object-creation overhead and having
+    to reinvent from scratch, just copy their compatibility routine.
+
+    """
+
+    def formatargandannotation(arg):
+        result = formatarg(arg)
+        if arg in annotations:
+            result += ": " + formatannotation(annotations[arg])
         return result
 
-
-else:
-    from inspect import formatargspec as inspect_formatargspec  # noqa
-
-
-if py3k:
-    from configparser import ConfigParser as SafeConfigParser
-    import configparser
-else:
-    from ConfigParser import SafeConfigParser  # noqa
-    import ConfigParser as configparser  # noqa
-
-if py2k:
-    from mako.util import parse_encoding
-
-if py3k:
-    import importlib.machinery
-
-    import importlib.util
-
-    def load_module_py(module_id, path):
-        spec = importlib.util.spec_from_file_location(module_id, path)
-        module = importlib.util.module_from_spec(spec)
-        spec.loader.exec_module(module)
-        return module
-
-    def load_module_pyc(module_id, path):
-        spec = importlib.util.spec_from_file_location(module_id, path)
-        module = importlib.util.module_from_spec(spec)
-        spec.loader.exec_module(module)
-        return module
-
-    def get_bytecode_suffixes():
-        try:
-            return importlib.machinery.BYTECODE_SUFFIXES
-        except AttributeError:
-            return importlib.machinery.DEBUG_BYTECODE_SUFFIXES
-
-    def get_current_bytecode_suffixes():
-        if py3k:
-            suffixes = importlib.machinery.BYTECODE_SUFFIXES
-        else:
-            if sys.flags.optimize:
-                suffixes = importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES
-            else:
-                suffixes = importlib.machinery.BYTECODE_SUFFIXES
-
-        return suffixes
-
-    def has_pep3147():
-        return True
-
-
-else:
-    import imp
-
-    def load_module_py(module_id, path):  # noqa
-        with open(path, "rb") as fp:
-            mod = imp.load_source(module_id, path, fp)
-            if py2k:
-                source_encoding = parse_encoding(fp)
-                if source_encoding:
-                    mod._alembic_source_encoding = source_encoding
-            del sys.modules[module_id]
-            return mod
-
-    def load_module_pyc(module_id, path):  # noqa
-        with open(path, "rb") as fp:
-            mod = imp.load_compiled(module_id, path, fp)
-            # no source encoding here
-            del sys.modules[module_id]
-            return mod
-
-    def get_current_bytecode_suffixes():
-        if sys.flags.optimize:
-            return [".pyo"]  # e.g. .pyo
-        else:
-            return [".pyc"]  # e.g. .pyc
-
-    def has_pep3147():
-        return False
-
-
-try:
-    exec_ = getattr(compat_builtins, "exec")
-except AttributeError:
-    # Python 2
-    def exec_(func_text, globals_, lcl):
-        exec("exec func_text in globals_, lcl")
-
-
-################################################
-# cross-compatible metaclass implementation
-# Copyright (c) 2010-2012 Benjamin Peterson
-
-
-def with_metaclass(meta, base=object):
-    """Create a base class with a metaclass."""
-    return meta("%sBase" % meta.__name__, (base,), {})
-
-
-################################################
-
-if py3k:
-
-    def raise_(
-        exception, with_traceback=None, replace_context=None, from_=False
-    ):
-        r"""implement "raise" with cause support.
-
-        :param exception: exception to raise
-        :param with_traceback: will call exception.with_traceback()
-        :param replace_context: an as-yet-unsupported feature.  This is
-         an exception object which we are "replacing", e.g., it's our
-         "cause" but we don't want it printed.    Basically just what
-         ``__suppress_context__`` does but we don't want to suppress
-         the enclosing context, if any.  So for now we make it the
-         cause.
-        :param from\_: the cause.  this actually sets the cause and doesn't
-         hope to hide it someday.
-
-        """
-        if with_traceback is not None:
-            exception = exception.with_traceback(with_traceback)
-
-        if from_ is not False:
-            exception.__cause__ = from_
-        elif replace_context is not None:
-            # no good solution here, we would like to have the exception
-            # have only the context of replace_context.__context__ so that the
-            # intermediary exception does not change, but we can't figure
-            # that out.
-            exception.__cause__ = replace_context
-
-        try:
-            raise exception
-        finally:
-            # credit to
-            # https://cosmicpercolator.com/2016/01/13/exception-leaks-in-python-2-and-3/
-            # as the __traceback__ object creates a cycle
-            del exception, replace_context, from_, with_traceback
-
-
-else:
-    exec(
-        "def raise_(exception, with_traceback=None, replace_context=None, "
-        "from_=False):\n"
-        "    if with_traceback:\n"
-        "        raise type(exception), exception, with_traceback\n"
-        "    else:\n"
-        "        raise exception\n"
-    )
+    specs = []
+    if defaults:
+        firstdefault = len(args) - len(defaults)
+    for i, arg in enumerate(args):
+        spec = formatargandannotation(arg)
+        if defaults and i >= firstdefault:
+            spec = spec + formatvalue(defaults[i - firstdefault])
+        specs.append(spec)
+    if varargs is not None:
+        specs.append(formatvarargs(formatargandannotation(varargs)))
+    else:
+        if kwonlyargs:
+            specs.append("*")
+    if kwonlyargs:
+        for kwonlyarg in kwonlyargs:
+            spec = formatargandannotation(kwonlyarg)
+            if kwonlydefaults and kwonlyarg in kwonlydefaults:
+                spec += formatvalue(kwonlydefaults[kwonlyarg])
+            specs.append(spec)
+    if varkw is not None:
+        specs.append(formatvarkw(formatargandannotation(varkw)))
+    result = "(" + ", ".join(specs) + ")"
+    if "return" in annotations:
+        result += formatreturns(formatannotation(annotations["return"]))
+    return result
 
 
 # produce a wrapper that allows encoded text to stream
@@ -318,43 +120,3 @@ else:
 class EncodedIO(io.TextIOWrapper):
     def close(self):
         pass
-
-
-if py2k:
-    # in Py2K, the io.* package is awkward because it does not
-    # easily wrap the file type (e.g. sys.stdout) and I can't
-    # figure out at all how to wrap StringIO.StringIO
-    # and also might be user specified too.  So create a full
-    # adapter.
-
-    class ActLikePy3kIO(object):
-
-        """Produce an object capable of wrapping either
-        sys.stdout (e.g. file) *or* StringIO.StringIO().
-
-        """
-
-        def _false(self):
-            return False
-
-        def _true(self):
-            return True
-
-        readable = seekable = _false
-        writable = _true
-        closed = False
-
-        def __init__(self, file_):
-            self.file_ = file_
-
-        def write(self, text):
-            return self.file_.write(text)
-
-        def flush(self):
-            return self.file_.flush()
-
-    class EncodedIO(EncodedIO):
-        def __init__(self, file_, encoding):
-            super(EncodedIO, self).__init__(
-                ActLikePy3kIO(file_), encoding=encoding
-            )
index 51041489c8b702d2a85034424fb0d7723663880c..c27f0f36b2eaaa83352fc824050b6e1ccf06f607 100644 (file)
@@ -5,7 +5,6 @@ from os.path import splitext
 from subprocess import check_call
 
 from .compat import is_posix
-from .compat import raise_
 from .exc import CommandError
 
 
@@ -28,7 +27,7 @@ def open_in_editor(filename, environ=None):
         editor = _find_editor(environ)
         check_call([editor, filename])
     except Exception as exc:
-        raise_(CommandError("Error executing editor (%s)" % (exc,)), from_=exc)
+        raise CommandError("Error executing editor (%s)" % (exc,)) from exc
 
 
 def _find_editor(environ=None):
index cc07f4b4ac10f082d9bbc43b157467161adf5e6b..dbd1f217a0dee55ebe4c98126eb37a02aada2402 100644 (file)
@@ -1,15 +1,17 @@
 import collections
+from collections.abc import Iterable
 import textwrap
 import uuid
 import warnings
 
-from .compat import callable
-from .compat import collections_abc
-from .compat import exec_
+from sqlalchemy.util import asbool  # noqa
+from sqlalchemy.util import immutabledict  # noqa
+from sqlalchemy.util import memoized_property  # noqa
+from sqlalchemy.util import to_list  # noqa
+from sqlalchemy.util import unique_list  # noqa
+
 from .compat import inspect_getargspec
-from .compat import raise_
 from .compat import string_types
-from .compat import with_metaclass
 
 
 class _ModuleClsMeta(type):
@@ -18,7 +20,7 @@ class _ModuleClsMeta(type):
         cls._update_module_proxies(key)
 
 
-class ModuleClsProxy(with_metaclass(_ModuleClsMeta)):
+class ModuleClsProxy(metaclass=_ModuleClsMeta):
     """Create module level proxy functions for the
     methods on a given class.
 
@@ -76,16 +78,13 @@ class ModuleClsProxy(with_metaclass(_ModuleClsMeta)):
         fn = getattr(cls, name)
 
         def _name_error(name, from_):
-            raise_(
-                NameError(
-                    "Can't invoke function '%s', as the proxy object has "
-                    "not yet been "
-                    "established for the Alembic '%s' class.  "
-                    "Try placing this code inside a callable."
-                    % (name, cls.__name__)
-                ),
-                from_=from_,
-            )
+            raise NameError(
+                "Can't invoke function '%s', as the proxy object has "
+                "not yet been "
+                "established for the Alembic '%s' class.  "
+                "Try placing this code inside a callable."
+                % (name, cls.__name__)
+            ) from from_
 
         globals_["_name_error"] = _name_error
 
@@ -160,7 +159,7 @@ class ModuleClsProxy(with_metaclass(_ModuleClsMeta)):
             }
         )
         lcl = {}
-        exec_(func_text, globals_, lcl)
+        exec(func_text, globals_, lcl)
         return lcl[name]
 
 
@@ -172,101 +171,26 @@ def _with_legacy_names(translations):
     return decorate
 
 
-def asbool(value):
-    return value is not None and value.lower() == "true"
-
-
 def rev_id():
     return uuid.uuid4().hex[-12:]
 
 
-def to_list(x, default=None):
-    if x is None:
-        return default
-    elif isinstance(x, string_types):
-        return [x]
-    elif isinstance(x, collections_abc.Iterable):
-        return list(x)
-    else:
-        return [x]
-
-
 def to_tuple(x, default=None):
     if x is None:
         return default
     elif isinstance(x, string_types):
         return (x,)
-    elif isinstance(x, collections_abc.Iterable):
+    elif isinstance(x, Iterable):
         return tuple(x)
     else:
         return (x,)
 
 
-def unique_list(seq, hashfunc=None):
-    seen = set()
-    seen_add = seen.add
-    if not hashfunc:
-        return [x for x in seq if x not in seen and not seen_add(x)]
-    else:
-        return [
-            x
-            for x in seq
-            if hashfunc(x) not in seen and not seen_add(hashfunc(x))
-        ]
-
-
 def dedupe_tuple(tup):
     return tuple(unique_list(tup))
 
 
-class memoized_property(object):
-
-    """A read-only @property that is only evaluated once."""
-
-    def __init__(self, fget, doc=None):
-        self.fget = fget
-        self.__doc__ = doc or fget.__doc__
-        self.__name__ = fget.__name__
-
-    def __get__(self, obj, cls):
-        if obj is None:
-            return self
-        obj.__dict__[self.__name__] = result = self.fget(obj)
-        return result
-
-
-class immutabledict(dict):
-    def _immutable(self, *arg, **kw):
-        raise TypeError("%s object is immutable" % self.__class__.__name__)
-
-    __delitem__ = (
-        __setitem__
-    ) = __setattr__ = clear = pop = popitem = setdefault = update = _immutable
-
-    def __new__(cls, *args):
-        new = dict.__new__(cls)
-        dict.__init__(new, *args)
-        return new
-
-    def __init__(self, *args):
-        pass
-
-    def __reduce__(self):
-        return immutabledict, (dict(self),)
-
-    def union(self, d):
-        if not self:
-            return immutabledict(d)
-        else:
-            d2 = immutabledict(self)
-            dict.update(d2, d)
-            return d2
-
-    def __repr__(self):
-        return "immutabledict(%s)" % dict.__repr__(self)
-
-
-class Dispatcher(object):
+class Dispatcher:
     def __init__(self, uselist=False):
         self._registry = {}
         self.uselist = uselist
index 29442d8135b61f97e3262b258673cff21e7f8f71..70c9128288399323534b21954ba4837d1965278d 100644 (file)
@@ -1,3 +1,4 @@
+from collections.abc import Iterable
 import logging
 import sys
 import textwrap
@@ -7,7 +8,6 @@ from sqlalchemy.engine import url
 
 from . import sqla_compat
 from .compat import binary_type
-from .compat import collections_abc
 from .compat import string_types
 
 log = logging.getLogger(__name__)
@@ -97,7 +97,7 @@ def format_as_comma(value):
         return ""
     elif isinstance(value, string_types):
         return value
-    elif isinstance(value, collections_abc.Iterable):
+    elif isinstance(value, Iterable):
         return ", ".join(value)
     else:
         raise ValueError("Don't know how to comma-format %r" % value)
index 1bc5be36c110c29d981778b33d2c73d6a44e5d35..53cc3cce44c21b7bb2d9aa719c330d05d7e65339 100644 (file)
@@ -1,3 +1,6 @@
+import importlib
+import importlib.machinery
+import importlib.util
 import os
 import re
 import tempfile
@@ -5,11 +8,6 @@ import tempfile
 from mako import exceptions
 from mako.template import Template
 
-from .compat import get_current_bytecode_suffixes
-from .compat import has_pep3147
-from .compat import load_module_py
-from .compat import load_module_pyc
-from .compat import py3k
 from .exc import CommandError
 
 
@@ -52,22 +50,14 @@ def coerce_resource_to_filename(fname):
 def pyc_file_from_path(path):
     """Given a python source path, locate the .pyc."""
 
-    if has_pep3147():
-        if py3k:
-            import importlib
-
-            candidate = importlib.util.cache_from_source(path)
-        else:
-            import imp
-
-            candidate = imp.cache_from_source(path)
-        if os.path.exists(candidate):
-            return candidate
+    candidate = importlib.util.cache_from_source(path)
+    if os.path.exists(candidate):
+        return candidate
 
     # even for pep3147, fall back to the old way of finding .pyc files,
     # to support sourceless operation
     filepath, ext = os.path.splitext(path)
-    for ext in get_current_bytecode_suffixes():
+    for ext in importlib.machinery.BYTECODE_SUFFIXES:
         if os.path.exists(filepath + ext):
             return filepath + ext
     else:
@@ -92,3 +82,17 @@ def load_python_file(dir_, filename):
     elif ext in (".pyc", ".pyo"):
         module = load_module_pyc(module_id, path)
     return module
+
+
+def load_module_py(module_id, path):
+    spec = importlib.util.spec_from_file_location(module_id, path)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module
+
+
+def load_module_pyc(module_id, path):
+    spec = importlib.util.spec_from_file_location(module_id, path)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module
index d5230626e70b07459accbf82ef17976a843cece6..a3de0f8c5ddb4fd09123ef15fc820dda8c1669d0 100644 (file)
@@ -307,7 +307,7 @@ We'll use a simple value object called ``ReplaceableObject`` that can
 represent any named set of SQL text to send to a "CREATE" statement of
 some kind::
 
-    class ReplaceableObject(object):
+    class ReplaceableObject:
         def __init__(self, name, sqltext):
             self.name = name
             self.sqltext = sqltext
@@ -1445,58 +1445,6 @@ branched revision tree::
 
     :meth:`.ScriptDirectory.get_heads`
 
-Support Non-Ascii Migration Scripts / Messages under Python 2
-==============================================================
-
-To work with a migration file that has non-ascii characters in it under Python
-2, the ``script.py.mako`` file inside of the Alembic environment has to have an
-encoding comment added to the top that will render into a ``.py`` file:
-
-.. code-block:: mako
-
-    <%text># coding: utf-8</%text>
-
-Additionally, individual fields if they are to have non-ascii characters in
-them may require decode operations on the template values.  Such as, if the
-revision message given on the command line to ``alembic revision`` has
-non-ascii characters in it, under Python 2 the command interface passes this
-through as bytes, and Alembic has no decode step built in for this as it is not
-necessary under Python 3.  To decode, add a decoding step to the template for
-each variable that potentially may have non-ascii characters within it.   An
-example of applying this to the "message" field is as follows:
-
-.. code-block:: mako
-
-    <%!
-        import sys
-    %>\
-    <%text># coding: utf-8</%text>
-    """${message.decode("utf-8") \
-       if sys.version_info < (3, ) \
-       and isinstance(message, str) else message}
-
-    Revision ID: ${up_revision}
-    Revises: ${down_revision | comma,n}
-    Create Date: ${create_date}
-
-    """
-    from alembic import op
-    import sqlalchemy as sa
-    ${imports if imports else ""}
-
-    # revision identifiers, used by Alembic.
-    revision = ${repr(up_revision)}
-    down_revision = ${repr(down_revision)}
-    branch_labels = ${repr(branch_labels)}
-    depends_on = ${repr(depends_on)}
-
-
-    def upgrade():
-        ${upgrades if upgrades else "pass"}
-
-
-    def downgrade():
-        ${downgrades if downgrades else "pass"}
 
 Using Asyncio with Alembic
 ==========================
index a56a85fba97ad4bdfb57028e79669ceb9a57acba..7f998ebd4a17771364e4058a27110f9205859a02 100644 (file)
@@ -83,9 +83,10 @@ SQLAlchemy as of version **1.3.0**.
 
 .. versionchanged:: 1.5.0 Support for SQLAlchemy older than 1.3.0 was dropped.
 
-Alembic supports Python versions 2.7, 3.6 and above.
+Alembic supports Python versions **3.6 and above**.
 
-.. versionchanged::  1.5.0  Support for Python 3.5 was dropped.
+.. versionchanged::  1.7  Alembic now supports Python 3.6 and newer; support
+   for Python 2.7 has been dropped.
 
 Community
 =========
diff --git a/docs/build/unreleased/py3.rst b/docs/build/unreleased/py3.rst
new file mode 100644 (file)
index 0000000..3a80bf8
--- /dev/null
@@ -0,0 +1,5 @@
+.. change::
+    :tags: python
+
+    Alembic 1.7 now supports Python 3.6 and above; support for prior versions
+    including Python 2.7 has been dropped.
index b168de5b6206857489c75e25171f7e911542d45c..7514d8bf2d735270de093937a7e8c67b15b6b174 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,8 +24,6 @@ classifiers =
     License :: OSI Approved :: MIT License
     Operating System :: OS Independent
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
@@ -39,7 +37,7 @@ classifiers =
 packages = find:
 include_package_data = true
 zip_safe = false
-python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
+python_requires = >=3.6
 
 install_requires =
     SQLAlchemy>=1.3.0
@@ -91,7 +89,8 @@ ignore =
 exclude = .venv,.git,.tox,dist,doc,*egg,build
 import-order-style = google
 application-import-names = alembic,tests
-
+per-file-ignores =
+                **/__init__.py:F401
 
 [sqla_testing]
 requirement_cls=tests.requirements:DefaultRequirements
index 1dff1e3e388c8def63aabd5bbeda8bc4bda7904e..f790be54caedc35f72208605533c7ac36f81e08e 100644 (file)
@@ -1,5 +1,3 @@
-import sys
-
 from sqlalchemy import BIGINT
 from sqlalchemy import BigInteger
 from sqlalchemy import Boolean
@@ -60,8 +58,6 @@ from alembic.util import CommandError
 # TODO: we should make an adaptation of CompareMetadataToInspectorTest that is
 #       more well suited towards generic backends (2021-06-10)
 
-py3k = sys.version_info >= (3,)
-
 
 class AutogenCrossSchemaTest(AutogenTest, TestBase):
     __only_on__ = "postgresql"
@@ -321,7 +317,7 @@ class AutogenDefaultSchemaIsNoneTest(AutogenFixtureTest, TestBase):
         eq_(len(diffs), 0)
 
 
-class ModelOne(object):
+class ModelOne:
     __requires__ = ("unique_constraint_reflection",)
 
     schema = None
@@ -1248,7 +1244,7 @@ class IncludeFiltersAPITest(AutogenTest, TestBase):
             object_filters=include_object, include_schemas=True
         )
 
-        class ExtFunction(object):
+        class ExtFunction:
             pass
 
         extfunc = ExtFunction()
index 80ad478c0d28487bdd61f0fc5a38880de9afedea..e25ddc20a8856349831a62601ec31a1e3f335cf5 100644 (file)
@@ -1,5 +1,3 @@
-import sys
-
 from sqlalchemy import Column
 from sqlalchemy import ForeignKey
 from sqlalchemy import ForeignKeyConstraint
@@ -29,10 +27,8 @@ from alembic.util import sqla_compat
 #       subset of the tests here. @zzzeek can work on this at a later point.
 #       (2021-06-10)
 
-py3k = sys.version_info >= (3,)
-
 
-class NoUqReflection(object):
+class NoUqReflection:
     __requires__ = ()
 
     def setUp(self):
index d4ce43283af588826a866218501fdcc4035cc003..67093284d75b0f18a2815466a441ee58bebda83d 100644 (file)
@@ -1,5 +1,4 @@
 import re
-import sys
 
 import sqlalchemy as sa  # noqa
 from sqlalchemy import BigInteger
@@ -49,9 +48,6 @@ from alembic.testing import eq_ignore_whitespace
 from alembic.testing import mock
 from alembic.testing import TestBase
 from alembic.testing.fixtures import op_fixture
-from alembic.util import compat
-
-py3k = sys.version_info >= (3,)
 
 
 class AutogenRenderTest(TestBase):
@@ -185,7 +181,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_index('test_lower_code_idx', 'test', "
-            "[sa.text(!U'lower(code)')], unique=False)",
+            "[sa.text('lower(code)')], unique=False)",
         )
 
     def test_render_add_index_cast(self):
@@ -202,7 +198,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_index('test_lower_code_idx', 'test', "
-            "[sa.text(!U'CAST(code AS VARCHAR)')], unique=False)",
+            "[sa.text('CAST(code AS VARCHAR)')], unique=False)",
         )
 
     def test_render_add_index_desc(self):
@@ -218,7 +214,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_index('test_desc_code_idx', 'test', "
-            "[sa.text(!U'code DESC')], unique=False)",
+            "[sa.text('code DESC')], unique=False)",
         )
 
     def test_drop_index(self):
@@ -832,7 +828,7 @@ class AutogenRenderTest(TestBase):
     def test_render_table_w_unicode_name(self):
         m = MetaData()
         t = Table(
-            compat.ue("\u0411\u0435\u0437"),
+            "\u0411\u0435\u0437",
             m,
             Column("id", Integer, primary_key=True),
         )
@@ -841,7 +837,7 @@ class AutogenRenderTest(TestBase):
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_table(%r,"
             "sa.Column('id', sa.Integer(), nullable=False),"
-            "sa.PrimaryKeyConstraint('id'))" % compat.ue("\u0411\u0435\u0437"),
+            "sa.PrimaryKeyConstraint('id'))" % "\u0411\u0435\u0437",
         )
 
     def test_render_table_w_unicode_schema(self):
@@ -850,7 +846,7 @@ class AutogenRenderTest(TestBase):
             "test",
             m,
             Column("id", Integer, primary_key=True),
-            schema=compat.ue("\u0411\u0435\u0437"),
+            schema="\u0411\u0435\u0437",
         )
         op_obj = ops.CreateTableOp.from_table(t)
         eq_ignore_whitespace(
@@ -858,7 +854,7 @@ class AutogenRenderTest(TestBase):
             "op.create_table('test',"
             "sa.Column('id', sa.Integer(), nullable=False),"
             "sa.PrimaryKeyConstraint('id'),"
-            "schema=%r)" % compat.ue("\u0411\u0435\u0437"),
+            "schema=%r)" % "\u0411\u0435\u0437",
         )
 
     def test_render_table_w_unsupported_constraint(self):
@@ -1193,7 +1189,7 @@ class AutogenRenderTest(TestBase):
         )
 
     def test_render_unicode_server_default(self):
-        default = compat.ue(
+        default = (
             "\u0411\u0435\u0437 "
             "\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f"
         )
@@ -1587,7 +1583,7 @@ class AutogenRenderTest(TestBase):
                 self.autogen_context,
                 None,
             ),
-            "sa.CheckConstraint(!U'im a constraint', name='cc1')",
+            "sa.CheckConstraint('im a constraint', name='cc1')",
         )
 
     def test_render_check_constraint_sqlexpr(self):
@@ -1600,7 +1596,7 @@ class AutogenRenderTest(TestBase):
                 self.autogen_context,
                 None,
             ),
-            "sa.CheckConstraint(!U'c > 5 AND c < 10')",
+            "sa.CheckConstraint('c > 5 AND c < 10')",
         )
 
     def test_render_check_constraint_literal_binds(self):
@@ -1611,7 +1607,7 @@ class AutogenRenderTest(TestBase):
                 self.autogen_context,
                 None,
             ),
-            "sa.CheckConstraint(!U'c > 5 AND c < 10')",
+            "sa.CheckConstraint('c > 5 AND c < 10')",
         )
 
     def test_render_unique_constraint_opts(self):
@@ -1632,13 +1628,13 @@ class AutogenRenderTest(TestBase):
             "t",
             m,
             Column("c", Integer),
-            schema=compat.ue("\u0411\u0435\u0437"),
+            schema="\u0411\u0435\u0437",
         )
         op_obj = ops.AddConstraintOp.from_constraint(UniqueConstraint(t.c.c))
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_unique_constraint(None, 't', ['c'], "
-            "schema=%r)" % compat.ue("\u0411\u0435\u0437"),
+            "schema=%r)" % "\u0411\u0435\u0437",
         )
 
     def test_render_modify_nullable_w_default(self):
@@ -1736,7 +1732,7 @@ class AutogenRenderTest(TestBase):
             "# ### commands auto generated by Alembic - please adjust! ###\n"
             "    op.create_table('sometable',\n"
             "    sa.Column('x', sa.DateTime(), "
-            "server_default=sa.text(!U'now()'), nullable=True)\n"
+            "server_default=sa.text('now()'), nullable=True)\n"
             "    )\n"
             "    # ### end Alembic commands ###",
         )
@@ -1756,7 +1752,7 @@ class AutogenRenderTest(TestBase):
             "# ### commands auto generated by Alembic - please adjust! ###\n"
             "    op.create_table('sometable',\n"
             "    sa.Column('x', sa.DateTime(), "
-            "server_default=sa.text(!U'(CURRENT_TIMESTAMP)'), nullable=True)\n"
+            "server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True)\n"
             "    )\n"
             "    # ### end Alembic commands ###",
         )
@@ -1851,7 +1847,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('updated_at', sa.TIMESTAMP(), "
-            "server_default=sa.text(!U'now()'), "
+            "server_default=sa.text('now()'), "
             "nullable=False)",
         )
 
@@ -1864,7 +1860,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('updated_at', sa.Boolean(), "
-            "server_default=sa.text(!U'0'), "
+            "server_default=sa.text('0'), "
             "nullable=False)",
         )
 
@@ -1879,7 +1875,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('updated_at', sa.TIMESTAMP(), "
-            "server_default=sa.text(!U'now()'), "
+            "server_default=sa.text('now()'), "
             "nullable=False)",
         )
 
@@ -1904,7 +1900,7 @@ class AutogenRenderTest(TestBase):
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.alter_column('sometable', 'somecolumn', "
             "existing_type=sa.Integer(), nullable=True, "
-            "existing_server_default=sa.text(!U'5'))",
+            "existing_server_default=sa.text('5'))",
         )
 
     def test_render_executesql_plaintext(self):
@@ -2076,7 +2072,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.add_column('foo', sa.Column('x', sa.Integer(), "
-            "sa.Computed(!U'5', ), nullable=True))",
+            "sa.Computed('5', ), nullable=True))",
         )
 
     @config.requirements.computed_columns_api
@@ -2088,7 +2084,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.add_column('foo', sa.Column('x', sa.Integer(), "
-            "sa.Computed(!U'5', persisted=%s), nullable=True))" % persisted,
+            "sa.Computed('5', persisted=%s), nullable=True))" % persisted,
         )
 
     @config.requirements.computed_columns_api
@@ -2099,7 +2095,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.alter_column('sometable', 'somecolumn', "
-            "server_default=sa.Computed(!U'7', ))",
+            "server_default=sa.Computed('7', ))",
         )
 
     @config.requirements.computed_columns_api
@@ -2112,7 +2108,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.alter_column('sometable', 'somecolumn', "
-            "existing_server_default=sa.Computed(!U'42', ))",
+            "existing_server_default=sa.Computed('42', ))",
         )
 
     @config.requirements.computed_columns_api
@@ -2128,7 +2124,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.alter_column('sometable', 'somecolumn', server_default"
-            "=sa.Computed(!U'7', persisted=%s))" % persisted,
+            "=sa.Computed('7', persisted=%s))" % persisted,
         )
 
     @config.requirements.computed_columns_api
@@ -2143,7 +2139,7 @@ class AutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.alter_column('sometable', 'somecolumn', "
-            "existing_server_default=sa.Computed(!U'42', persisted=%s))"
+            "existing_server_default=sa.Computed('42', persisted=%s))"
             % persisted,
         )
 
@@ -2364,7 +2360,7 @@ class RenderNamingConventionTest(TestBase):
         eq_ignore_whitespace(
             autogenerate.render_op_text(self.autogen_context, op_obj),
             "op.create_table('t',sa.Column('c', sa.Integer(), nullable=True),"
-            "sa.CheckConstraint(!U'c > 5', name=op.f('ck_ct_t')))",
+            "sa.CheckConstraint('c > 5', name=op.f('ck_ct_t')))",
         )
 
     def test_inline_fk(self):
@@ -2398,7 +2394,7 @@ class RenderNamingConventionTest(TestBase):
             autogenerate.render._render_check_constraint(
                 ck, self.autogen_context, None
             ),
-            "sa.CheckConstraint(!U'im a constraint', name=op.f('ck_t_cc1'))",
+            "sa.CheckConstraint('im a constraint', name=op.f('ck_t_cc1'))",
         )
 
     def test_create_table_plus_add_index_in_modify(self):
index a477a1d3aac54122662d1a460f4c22778e8c3395..98477eec6c1857baa6c8aa363aaff984d0e22392 100644 (file)
@@ -1,6 +1,7 @@
 from contextlib import contextmanager
 import inspect
 from io import BytesIO
+from io import StringIO
 from io import TextIOWrapper
 import os
 import re
@@ -32,11 +33,10 @@ from alembic.testing.env import write_script
 from alembic.testing.fixtures import capture_context_buffer
 from alembic.testing.fixtures import capture_engine_context_buffer
 from alembic.testing.fixtures import TestBase
-from alembic.util import compat
 from alembic.util.sqla_compat import _connectable_has_table
 
 
-class _BufMixin(object):
+class _BufMixin:
     def _buf_fixture(self):
         # try to simulate how sys.stdout looks - we send it u''
         # but then it's trying to encode to something.
@@ -536,7 +536,7 @@ finally:
         command.revision(self.cfg, sql=True)
 
 
-class _StampTest(object):
+class _StampTest:
     def _assert_sql(self, emitted_sql, origin, destinations):
         ins_expr = (
             r"INSERT INTO alembic_version \(version_num\) "
@@ -1173,8 +1173,8 @@ class CommandLineTest(TestBase):
             )
 
     def test_version_text(self):
-        buf = compat.StringIO()
-        to_mock = "sys.stdout" if util.compat.py3k else "sys.stderr"
+        buf = StringIO()
+        to_mock = "sys.stdout"
 
         with mock.patch(to_mock, buf):
             try:
index 3b2e957a09f0c7442ebde2c94b46f374a59fc4ec..215ba12bb0dab139f3d2519ad522592c7871e0de 100644 (file)
@@ -15,7 +15,6 @@ from alembic.testing.env import clear_staging_env
 from alembic.testing.env import staging_env
 from alembic.testing.fixtures import capture_db
 from alembic.testing.fixtures import TestBase
-from alembic.util import compat
 
 
 class FileConfigTest(TestBase):
@@ -122,16 +121,16 @@ class StdoutOutputEncodingTest(TestBase):
     def test_utf8_unicode(self):
         stdout = mock.Mock(encoding="latin-1")
         cfg = config.Config(stdout=stdout)
-        cfg.print_stdout(compat.u("méil %s %s"), "x", "y")
+        cfg.print_stdout("méil %s %s", "x", "y")
         eq_(
             stdout.mock_calls,
-            [mock.call.write(compat.u("méil x y")), mock.call.write("\n")],
+            [mock.call.write("méil x y"), mock.call.write("\n")],
         )
 
     def test_ascii_unicode(self):
         stdout = mock.Mock(encoding=None)
         cfg = config.Config(stdout=stdout)
-        cfg.print_stdout(compat.u("méil %s %s"), "x", "y")
+        cfg.print_stdout("méil %s %s", "x", "y")
         eq_(
             stdout.mock_calls,
             [mock.call.write("m?il x y"), mock.call.write("\n")],
@@ -140,7 +139,7 @@ class StdoutOutputEncodingTest(TestBase):
     def test_only_formats_output_with_args(self):
         stdout = mock.Mock(encoding=None)
         cfg = config.Config(stdout=stdout)
-        cfg.print_stdout(compat.u("test 3%"))
+        cfg.print_stdout("test 3%")
         eq_(
             stdout.mock_calls,
             [mock.call.write("test 3%"), mock.call.write("\n")],
index 4a58b8fb4612cb8fce6cb6a63f80c51e34053c91..0ec6f5f43f43523739b477959207631184c284f6 100644 (file)
@@ -1,23 +1,21 @@
 import os
 from os.path import join
+from unittest.mock import patch
 
 from alembic import util
 from alembic.testing import combinations
 from alembic.testing import expect_raises_message
-from alembic.testing import mock
 from alembic.testing.fixtures import TestBase
 
 
 class TestHelpers(TestBase):
     def common(self, cb, is_posix=True):
-        with mock.patch(
-            "alembic.util.editor.check_call"
-        ) as check_call, mock.patch(
+        with patch("alembic.util.editor.check_call") as check_call, patch(
             "alembic.util.editor.exists"
-        ) as exists, mock.patch(
+        ) as exists, patch(
             "alembic.util.editor.is_posix",
             new=is_posix,
-        ), mock.patch(
+        ), patch(
             "os.pathsep", new=":" if is_posix else ";"
         ):
             cb(check_call, exists)
index 10f17d462e5a846926c9d94f7d1b38eb90ca5685..500678a6b545c318d5e5402d3e7163cf21d1b71a 100644 (file)
@@ -912,7 +912,7 @@ class PostgresqlAutogenRenderTest(TestBase):
             autogenerate.render_op_text(autogen_context, op_obj),
             """op.create_index('foo_idx', 't', \
 ['x', 'y'], unique=False, """
-            """postgresql_where=sa.text(!U"y = 'something'"))""",
+            """postgresql_where=sa.text("y = 'something'"))""",
         )
 
     def test_render_server_default_native_boolean(self):
@@ -923,7 +923,7 @@ class PostgresqlAutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('updated_at', sa.Boolean(), "
-            "server_default=sa.text(!U'false'), "
+            "server_default=sa.text('false'), "
             "nullable=False)",
         )
 
@@ -1049,7 +1049,7 @@ class PostgresqlAutogenRenderTest(TestBase):
             autogenerate.render_op_text(autogen_context, op_obj),
             "op.create_exclude_constraint('t_excl_x', "
             "'t', (sa.column('x'), '>'), "
-            "where=sa.text(!U'x != 2'), using='gist')",
+            "where=sa.text('x != 2'), using='gist')",
         )
 
     def test_add_exclude_constraint_case_sensitive(self):
@@ -1075,7 +1075,7 @@ class PostgresqlAutogenRenderTest(TestBase):
             autogenerate.render_op_text(autogen_context, op_obj),
             "op.create_exclude_constraint('t_excl_x', 'TTAble', "
             "(sa.column('XColumn'), '>'), "
-            "where=sa.text(!U'\"XColumn\" != 2'), using='gist')",
+            "where=sa.text('\"XColumn\" != 2'), using='gist')",
         )
 
     def test_inline_exclude_constraint(self):
@@ -1104,7 +1104,7 @@ class PostgresqlAutogenRenderTest(TestBase):
             "op.create_table('t',sa.Column('x', sa.String(), nullable=True),"
             "sa.Column('y', sa.String(), nullable=True),"
             "postgresql.ExcludeConstraint((sa.column('x'), '>'), "
-            "where=sa.text(!U'x != 2'), using='gist', name='t_excl_x')"
+            "where=sa.text('x != 2'), using='gist', name='t_excl_x')"
             ")",
         )
 
@@ -1132,7 +1132,7 @@ class PostgresqlAutogenRenderTest(TestBase):
             "nullable=True),"
             "sa.Column('YColumn', sa.String(), nullable=True),"
             "postgresql.ExcludeConstraint((sa.column('XColumn'), '>'), "
-            "where=sa.text(!U'\"XColumn\" != 2'), using='gist', "
+            "where=sa.text('\"XColumn\" != 2'), using='gist', "
             "name='TExclX'))",
         )
 
index e7eda64856a1e3641529724fa977a45eab278634..4b46564dc5a1fc8a7a5e2e70d7e6a9c8d080b635 100644 (file)
@@ -31,7 +31,7 @@ from alembic.testing.fixtures import TestBase
 from alembic.util import compat
 
 
-class PatchEnvironment(object):
+class PatchEnvironment:
     branched_connection = False
 
     @contextmanager
@@ -301,8 +301,7 @@ class SimpleSourcelessApplyVersionsTest(ApplyVersionsFunctionalTest):
     id_="r",
 )
 class NewFangledSourcelessApplyVersionsTest(ApplyVersionsFunctionalTest):
-
-    __requires__ = ("pep3147",)
+    pass
 
 
 class CallbackEnvironmentTest(ApplyVersionsFunctionalTest):
@@ -644,8 +643,7 @@ class EncodingTest(TestBase):
             script,
             self.a,
             (
-                compat.u(
-                    """# coding: utf-8
+                """# coding: utf-8
 from __future__ import unicode_literals
 revision = '%s'
 down_revision = None
@@ -659,7 +657,6 @@ def downgrade():
     op.execute("drôle de petite voix m’a réveillé")
 
 """
-                )
                 % self.a
             ),
             encoding="utf-8",
@@ -673,7 +670,7 @@ def downgrade():
             bytes_io=True, output_encoding="utf-8"
         ) as buf:
             command.upgrade(self.cfg, self.a, sql=True)
-        assert compat.u("« S’il vous plaît…").encode("utf-8") in buf.getvalue()
+        assert "« S’il vous plaît…".encode("utf-8") in buf.getvalue()
 
 
 class VersionNameTemplateTest(TestBase):
@@ -847,14 +844,10 @@ class SimpleSourcelessIgnoreFilesTest(IgnoreFilesTest):
 class NewFangledEnvOnlySourcelessIgnoreFilesTest(IgnoreFilesTest):
     sourceless = "pep3147_envonly"
 
-    __requires__ = ("pep3147",)
-
 
 class NewFangledEverythingSourcelessIgnoreFilesTest(IgnoreFilesTest):
     sourceless = "pep3147_everything"
 
-    __requires__ = ("pep3147",)
-
 
 class SourcelessNeedsFlagTest(TestBase):
     def setUp(self):
index 946f69f64bc1be4a31b72fc729fcaba3d38afddf..3915343d1bf3339258fa21c221292eb27dcd2656 100644 (file)
@@ -230,7 +230,7 @@ class SQLiteAutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('date_value', sa.DateTime(), "
-            "server_default=sa.text(!U\"(datetime('now', 'localtime'))\"), "
+            "server_default=sa.text(\"(datetime('now', 'localtime'))\"), "
             "nullable=True)",
         )
 
@@ -245,7 +245,7 @@ class SQLiteAutogenRenderTest(TestBase):
         eq_ignore_whitespace(
             result,
             "sa.Column('date_value', sa.DateTime(), "
-            "server_default=sa.text(!U\"(datetime('now', 'localtime'))\"), "
+            "server_default=sa.text(\"(datetime('now', 'localtime'))\"), "
             "nullable=True)",
         )
 
diff --git a/tox.ini b/tox.ini
index 596942a8a7c535b3e8ff8879a241a56e348b52dd..e52e1a426286e90d04f8641105ae950636374d88 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -16,8 +16,7 @@ deps=pytest>4.6
      postgresql: psycopg2>=2.7
      mysql: mysqlclient>=1.4.0
      mysql: pymysql
-     oracle: cx_oracle>=7,<8;python_version<"3"
-     oracle: cx_oracle>=7;python_version>="3"
+     oracle: cx_oracle>=7
      mssql: pyodbc
      cov: pytest-cov
      sqlalchemy: sqlalchemy>=1.3.0