From e80d1d2299f69e1f4a4e91af70d1244a32c39c65 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 23 Aug 2021 16:24:26 -0400 Subject: remove dependency on pkg_resources 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 | 17 +++++++++++------ alembic/util/compat.py | 22 +++++++++++++++++++++- alembic/util/pyfiles.py | 18 ++++++++++++++++-- docs/build/unreleased/885.rst | 10 ++++++++++ setup.cfg | 2 ++ tests/test_post_write.py | 16 +++++++++++----- 6 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 docs/build/unreleased/885.rst diff --git a/alembic/script/write_hooks.py b/alembic/script/write_hooks.py index 8990148..7c8acab 100644 --- a/alembic/script/write_hooks.py +++ b/alembic/script/write_hooks.py @@ -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, ) diff --git a/alembic/util/compat.py b/alembic/util/compat.py index dae98f4..f83901f 100644 --- a/alembic/util/compat.py +++ b/alembic/util/compat.py @@ -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, ()) diff --git a/alembic/util/pyfiles.py b/alembic/util/pyfiles.py index 7eb582e..3da32c3 100644 --- a/alembic/util/pyfiles.py +++ b/alembic/util/pyfiles.py @@ -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 index 0000000..cf010a3 --- /dev/null +++ b/docs/build/unreleased/885.rst @@ -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. diff --git a/setup.cfg b/setup.cfg index d2df66c..fab504d 100644 --- 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 = diff --git a/tests/test_post_write.py b/tests/test_post_write.py index 18c1d82..82a9174 100644 --- a/tests/test_post_write.py +++ b/tests/test_post_write.py @@ -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, -- cgit v1.2.1