Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changed version_option default package from pkg_resources to importli… #1582

Merged
merged 3 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Unreleased
- Add an optional parameter to ``ProgressBar.update`` to set the
``current_item``. :issue:`1226`, :pr:`1332`
- Include ``--help`` option in completion. :pr:`1504`
- ``version_option`` uses ``importlib.metadata`` (or the
``importlib_metadata`` backport) instead of ``pkg_resources``.
:issue:`1582`


Version 7.1.2
Expand Down
2 changes: 2 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ filelock==3.0.12 # via tox, virtualenv
identify==1.4.16 # via pre-commit
idna==2.9 # via requests
imagesize==1.2.0 # via sphinx
importlib-metadata==1.6.1 # via -r requirements/tests.in
jinja2==2.11.2 # via sphinx
markupsafe==1.1.1 # via jinja2
more-itertools==8.3.0 # via pytest
Expand Down Expand Up @@ -51,6 +52,7 @@ tox==3.15.2 # via -r requirements/dev.in
urllib3==1.25.9 # via requests
virtualenv==20.0.21 # via pre-commit, tox
wcwidth==0.1.9 # via pytest
zipp==3.1.0 # via importlib-metadata

# The following packages are considered to be unsafe in a requirements file:
# pip
Expand Down
1 change: 1 addition & 0 deletions requirements/tests.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pytest
colorama
importlib_metadata
2 changes: 2 additions & 0 deletions requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#
attrs==19.3.0 # via pytest
colorama==0.4.3 # via -r requirements/tests.in
importlib-metadata==1.6.1 # via -r requirements/tests.in
more-itertools==8.3.0 # via pytest
packaging==20.4 # via pytest
pluggy==0.13.1 # via pytest
Expand All @@ -14,3 +15,4 @@ pyparsing==2.4.7 # via packaging
pytest==5.4.3 # via -r requirements/tests.in
six==1.15.0 # via packaging
wcwidth==0.1.9 # via pytest
zipp==3.1.0 # via importlib-metadata
150 changes: 102 additions & 48 deletions src/click/decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import inspect
import sys
from functools import update_wrapper

from .core import Argument
Expand Down Expand Up @@ -248,60 +247,115 @@ def decorator(f):
return decorator


def version_option(version=None, *param_decls, **attrs):
"""Adds a ``--version`` option which immediately ends the program
printing out the version number. This is implemented as an eager
option that prints the version and exits the program in the callback.

:param version: the version number to show. If not provided Click
attempts an auto discovery via setuptools.
:param prog_name: the name of the program (defaults to autodetection)
:param message: custom message to show instead of the default
(``'%(prog)s, version %(version)s'``)
:param others: everything else is forwarded to :func:`option`.
def version_option(
version=None,
*param_decls,
package_name=None,
prog_name=None,
message="%(prog)s, version %(version)s",
**kwargs,
):
"""Add a ``--version`` option which immediately prints the version
number and exits the program.

If ``version`` is not provided, Click will try to detect it using
:func:`importlib.metadata.version` to get the version for the
``package_name``. On Python < 3.8, the ``importlib_metadata``
backport must be installed.

If ``package_name`` is not provided, Click will try to detect it by
inspecting the stack frames. This will be used to detect the
version, so it must match the name of the installed package.

:param version: The version number to show. If not provided, Click
will try to detect it.
:param param_decls: One or more option names. Defaults to the single
value ``"--version"``.
:param package_name: The package name to detect the version from. If
not provided, Click will try to detect it.
:param prog_name: The name of the CLI to show in the message. If not
provided, it will be detected from the command.
:param message: The message to show. The values ``%(prog)s``,
``%(package)s``, and ``%(version)s`` are available.
:param kwargs: Extra arguments are passed to :func:`option`.
:raise RuntimeError: ``version`` could not be detected.

.. versionchanged:: 8.0
Add the ``package_name`` parameter, and the ``%(package)s``
value for messages.

.. versionchanged:: 8.0
Use :mod:`importlib.metadata` instead of ``pkg_resources``.
"""
if version is None:
if hasattr(sys, "_getframe"):
module = sys._getframe(1).f_globals.get("__name__")
else:
module = ""
if version is None and package_name is None:
frame = inspect.currentframe()
f_globals = frame.f_back.f_globals if frame is not None else None
# break reference cycle
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
del frame

def decorator(f):
prog_name = attrs.pop("prog_name", None)
message = attrs.pop("message", "%(prog)s, version %(version)s")
if f_globals is not None:
package_name = f_globals.get("__name__")

def callback(ctx, param, value):
if not value or ctx.resilient_parsing:
return
prog = prog_name
if prog is None:
prog = ctx.find_root().info_name
ver = version
if ver is None:
if package_name == "__main__":
package_name = f_globals.get("__package__")

if package_name:
package_name = package_name.partition(".")[0]

def callback(ctx, param, value):
if not value or ctx.resilient_parsing:
return

nonlocal prog_name
nonlocal version

if prog_name is None:
prog_name = ctx.find_root().info_name

if version is None and package_name is not None:
try:
from importlib import metadata
except ImportError:
# Python < 3.8
try:
import pkg_resources
import importlib_metadata as metadata
except ImportError:
pass
else:
for dist in pkg_resources.working_set:
scripts = dist.get_entry_map().get("console_scripts") or {}
for entry_point in scripts.values():
if entry_point.module_name == module:
ver = dist.version
break
if ver is None:
raise RuntimeError("Could not determine version")
echo(message % {"prog": prog, "version": ver}, color=ctx.color)
ctx.exit()
metadata = None

attrs.setdefault("is_flag", True)
attrs.setdefault("expose_value", False)
attrs.setdefault("is_eager", True)
attrs.setdefault("help", "Show the version and exit.")
attrs["callback"] = callback
return option(*(param_decls or ("--version",)), **attrs)(f)
if metadata is None:
raise RuntimeError(
"Install 'importlib_metadata' to get the version on Python < 3.8."
)

return decorator
try:
version = metadata.version(package_name)
except metadata.PackageNotFoundError:
raise RuntimeError(
f"{package_name!r} is not installed. Try passing"
" 'package_name' instead."
)

if version is None:
raise RuntimeError(
f"Could not determine the version for {package_name!r} automatically."
)

echo(
message % {"prog": prog_name, "package": package_name, "version": version},
color=ctx.color,
)
ctx.exit()

if not param_decls:
param_decls = ("--version",)

kwargs.setdefault("is_flag", True)
kwargs.setdefault("expose_value", False)
kwargs.setdefault("is_eager", True)
kwargs.setdefault("help", "Show the version and exit.")
kwargs["callback"] = callback
return option(*param_decls, **kwargs)


def help_option(*param_decls, **attrs):
Expand Down