]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
remove dependency on pkg_resources
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 23 Aug 2021 20:24:26 +0000 (16:24 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 23 Aug 2021 20:45:06 +0000 (16:45 -0400)
The dependency on ``pkg_resources`` which is part of ``setuptools`` has
been removed, so there is no longer any runtime dependency on
``setuptools``. The functionality has been replaced with
``importlib.metadata`` and ``importlib.resources`` which are both part of
Python std.lib, or via pypy dependency ``importlib-metadata`` for Python
version < 3.8 and ``importlib-resources`` for Python version < 3.7.

Change-Id: I7802fe72ff644f52ae2edde53bc8e16f016b7c45
Fixes: #885
alembic/script/write_hooks.py
alembic/util/compat.py
alembic/util/pyfiles.py
docs/build/unreleased/885.rst [new file with mode: 0644]
setup.cfg
tests/test_post_write.py

index 89901488bc4c866d95096acffa993e6b55a89064..7c8acab1b500616f2f5e69d73034f980d9ce4ddd 100644 (file)
@@ -114,7 +114,6 @@ def _parse_cmdline_options(cmdline_options_str: str, path: str) -> List[str]:
 
 @register("console_scripts")
 def console_scripts(path, options, ignore_output=False):
-    import pkg_resources
 
     try:
         entrypoint_name = options["entrypoint"]
@@ -123,8 +122,14 @@ def console_scripts(path, options, ignore_output=False):
             "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_)
+    for entry in compat.importlib_metadata_get("console_scripts"):
+        if entry.name == entrypoint_name:
+            impl = entry
+            break
+    else:
+        raise util.CommandError(
+            f"Could not find entrypoint console_scripts.{entrypoint_name}"
+        )
     cwd = options.get("cwd", None)
     cmdline_options_str = options.get("options", "")
     cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path)
@@ -132,14 +137,14 @@ def console_scripts(path, options, ignore_output=False):
     kw = {}
     if ignore_output:
         kw["stdout"] = kw["stderr"] = subprocess.DEVNULL
+
     subprocess.run(
         [
             sys.executable,
             "-c",
-            "import %s; %s()"
-            % (impl.module_name, ".".join((impl.module_name,) + impl.attrs)),
+            "import %s; %s.%s()" % (impl.module, impl.module, impl.attr),
         ]
         + cmdline_options_list,
         cwd=cwd,
-        **kw
+        **kw,
     )
index dae98f472381b24df28dd11e78a82dbc20e9e95d..f83901f75602523372f925db9b9278a6a2adfba9 100644 (file)
@@ -8,7 +8,8 @@ from sqlalchemy.util.compat import inspect_formatargspec  # noqa
 is_posix = os.name == "posix"
 
 py39 = sys.version_info >= (3, 9)
-
+py38 = sys.version_info >= (3, 8)
+py37 = sys.version_info >= (3, 7)
 
 string_types = (str,)
 binary_type = bytes
@@ -21,3 +22,22 @@ text_type = str
 class EncodedIO(io.TextIOWrapper):
     def close(self) -> None:
         pass
+
+
+if py37:
+    from importlib import resources as importlib_resources
+else:
+    import importlib_resources  # noqa
+
+if py38:
+    from importlib import metadata as importlib_metadata
+else:
+    import importlib_metadata  # noqa
+
+
+def importlib_metadata_get(group):
+    ep = importlib_metadata.entry_points()
+    if hasattr(ep, "select"):
+        return ep.select(group=group)
+    else:
+        return ep.get(group, ())
index 7eb582eff8533a4b61d35b40e387ba3934cbede2..3da32c3141b75b16c970faf1adb87899eb21d98f 100644 (file)
@@ -1,3 +1,5 @@
+import atexit
+from contextlib import ExitStack
 import importlib
 import importlib.machinery
 import importlib.util
@@ -9,6 +11,7 @@ from typing import Optional
 from mako import exceptions
 from mako.template import Template
 
+from . import compat
 from .exc import CommandError
 
 
@@ -44,9 +47,20 @@ def coerce_resource_to_filename(fname: str) -> str:
 
     """
     if not os.path.isabs(fname) and ":" in fname:
-        import pkg_resources
 
-        fname = pkg_resources.resource_filename(*fname.split(":"))
+        tokens = fname.split(":")
+
+        # from https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-filename  # noqa E501
+
+        file_manager = ExitStack()
+        atexit.register(file_manager.close)
+
+        ref = compat.importlib_resources.files(tokens[0])
+        for tok in tokens[1:]:
+            ref = ref / tok
+        fname = file_manager.enter_context(
+            compat.importlib_resources.as_file(ref)
+        )
     return fname
 
 
diff --git a/docs/build/unreleased/885.rst b/docs/build/unreleased/885.rst
new file mode 100644 (file)
index 0000000..cf010a3
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, general
+    :tickets: 885
+
+    The dependency on ``pkg_resources`` which is part of ``setuptools`` has
+    been removed, so there is no longer any runtime dependency on
+    ``setuptools``. The functionality has been replaced with
+    ``importlib.metadata`` and ``importlib.resources`` which are both part of
+    Python std.lib, or via pypy dependency ``importlib-metadata`` for Python
+    version < 3.8 and ``importlib-resources`` for Python version < 3.7.
index d2df66c50c5d9d219b6d7efca770129fd81944cd..fab504dd5796024a138232e4c9123f6cc073c3af 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -42,6 +42,8 @@ python_requires = >=3.6
 install_requires =
     SQLAlchemy>=1.3.0
     Mako
+    importlib-metadata;python_version<"3.8"
+    importlib-resources;python_version<"3.7"
 
 [options.extras_require]
 tz =
index 18c1d82c8919ad6a1d29a2553fc1106dfdb00733..82a91743a95fcede0288915862792be3af8346d1 100644 (file)
@@ -133,17 +133,23 @@ class RunHookTest(TestBase):
         self, input_config, expected_additional_arguments_fn, cwd=None
     ):
         self.cfg = _no_sql_testing_config(directives=input_config)
-        impl = mock.Mock(attrs=("foo", "bar"), module_name="black_module")
-        entrypoints = mock.Mock(return_value=iter([impl]))
+
+        class MocksCantName:
+            name = "black"
+            attr = "bar"
+            module = "black_module.foo"
+
+        importlib_metadata_get = mock.Mock(return_value=iter([MocksCantName]))
         with mock.patch(
-            "pkg_resources.iter_entry_points", entrypoints
+            "alembic.util.compat.importlib_metadata_get",
+            importlib_metadata_get,
         ), mock.patch(
             "alembic.script.write_hooks.subprocess"
         ) as mock_subprocess:
 
             rev = command.revision(self.cfg, message="x")
 
-        eq_(entrypoints.mock_calls, [mock.call("console_scripts", "black")])
+        eq_(importlib_metadata_get.mock_calls, [mock.call("console_scripts")])
         eq_(
             mock_subprocess.mock_calls,
             [
@@ -151,7 +157,7 @@ class RunHookTest(TestBase):
                     [
                         sys.executable,
                         "-c",
-                        "import black_module; black_module.foo.bar()",
+                        "import black_module.foo; black_module.foo.bar()",
                     ]
                     + expected_additional_arguments_fn(rev.path),
                     cwd=cwd,