jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/985365 )
Change subject: replace pkg_resources with importlib.metadata and packaging ......................................................................
replace pkg_resources with importlib.metadata and packaging
Bug: T340640 Change-Id: Ib4e9921b8721225bb477e9521ab337c30a0d8450 --- M pywikibot/tools/__init__.py M setup.py M pywikibot/scripts/wrapper.py M pywikibot/backports.py M .coveragerc 5 files changed, 60 insertions(+), 94 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/.coveragerc b/.coveragerc index 5e64004..dd6f136 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,7 +8,6 @@ except ImportError except KeyboardInterrupt except OSError - except pkg_resources except \w*ServerError except SyntaxError raise ImportError diff --git a/pywikibot/backports.py b/pywikibot/backports.py index 04a334e..2b2aa4e 100644 --- a/pywikibot/backports.py +++ b/pywikibot/backports.py @@ -184,6 +184,12 @@ from itertools import batched # type: ignore[no-redef]
+try: + import importlib.metadata as importlib_metadata +except ImportError: + import importlib_metadata + + # import ModuleDeprecationWrapper here to prevent circular import from pywikibot.tools import ModuleDeprecationWrapper # noqa: E402
diff --git a/pywikibot/scripts/wrapper.py b/pywikibot/scripts/wrapper.py index 5b08cc8..3b24e25 100755 --- a/pywikibot/scripts/wrapper.py +++ b/pywikibot/scripts/wrapper.py @@ -44,7 +44,6 @@ # from __future__ import annotations
-# import os import sys import types @@ -59,7 +58,7 @@ site_package = False
-def check_pwb_versions(package): +def check_pwb_versions(package: str): """Validate package version and scripts version.
Rules: @@ -67,7 +66,8 @@ - Scripts version must not be older than previous Pywikibot version due to deprecation policy """ - from pywikibot.tools import Version + from packaging.version import Version + scripts_version = Version(getattr(package, '__version__', pwb.__version__)) wikibot_version = Version(pwb.__version__)
@@ -218,19 +218,18 @@ print(f" pip install "{str(requirement).partition(';')[0]}"\n")
-def check_modules(script=None): +def check_modules(script: str | None = None) -> bool: """Check whether mandatory modules are present.
This also checks Python version when importing dependencies from setup.py
:param script: The script name to be checked for dependencies - :type script: str or None :return: True if all dependencies are installed - :rtype: bool :raise RuntimeError: wrong Python version found in setup.py """ - import pkg_resources + from packaging.requirements import Requirement
+ from pywikibot.backports import importlib_metadata from setup import script_deps
missing_requirements = [] @@ -240,35 +239,25 @@ dependencies = script_deps.get(Path(script).name, []) else: from setup import dependencies - try: - next(pkg_resources.parse_requirements(dependencies)) - except ValueError as e: # pragma: no cover - # T286980: setuptools is too old and requirement parsing fails - import setuptools - setupversion = tuple(int(num) - for num in setuptools.__version__.split('.')) - if setupversion < (20, 8, 1): - # print the minimal requirement - _print_requirements( - ['setuptools>=20.8.1'], None, - f'outdated ({setuptools.__version__})') - return False - raise e
- for requirement in pkg_resources.parse_requirements(dependencies): - if requirement.marker is None \ - or pkg_resources.evaluate_marker(str(requirement.marker)): + for dependency in dependencies: + requirement = Requirement(dependency) + if requirement.marker is None or requirement.marker.evaluate(): try: - pkg_resources.resource_exists(requirement, requirement.name) - except pkg_resources.DistributionNotFound as e: + instlld_vrsn = importlib_metadata.version(requirement.name) + except importlib_metadata.PackageNotFoundError as e: missing_requirements.append(requirement) print(e) - except pkg_resources.VersionConflict as e: - version_conflicts.append(requirement) - print(e) + else: + if instlld_vrsn not in requirement.specifier: + version_conflicts.append(requirement) + print( + f'{requirement.name} version {instlld_vrsn} is ' + f'installed but {requirement.specifier} is required' + )
- del pkg_resources - del dependencies + del Requirement + del importlib_metadata del script_deps
_print_requirements(missing_requirements, script, 'missing') diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py index caa8763..088120c 100644 --- a/pywikibot/tools/__init__.py +++ b/pywikibot/tools/__init__.py @@ -35,15 +35,14 @@ import sys from contextlib import suppress from functools import total_ordering, wraps -from importlib import import_module from types import TracebackType from typing import Any from warnings import catch_warnings, showwarning, warn
-import pkg_resources +import packaging.version
import pywikibot # T306760 -from pywikibot.backports import Callable +from pywikibot.backports import Callable, importlib_metadata from pywikibot.tools._deprecate import ( ModuleDeprecationWrapper, add_decorated_full_name, @@ -60,9 +59,6 @@ from pywikibot.tools._unidata import _first_upper_exception
-pkg_Version = pkg_resources.packaging.version.Version # noqa: N816 - - __all__ = ( # deprecating functions 'ModuleDeprecationWrapper', @@ -89,7 +85,6 @@ 'first_upper', 'strtobool', 'normalize_username', - 'Version', 'MediaWikiVersion', 'open_archive', 'merge_unique_dicts', @@ -117,7 +112,7 @@ return False
-def has_module(module, version=None) -> bool: +def has_module(module: str, version: str | None = None) -> bool: """Check if a module can be imported.
.. versionadded:: 3.0 @@ -127,15 +122,13 @@ removed with Python 3.12. """ try: - m = import_module(module) - except ImportError: + metadata_version = importlib_metadata.version(module) + except importlib_metadata.PackageNotFoundError: return False if version: - if not hasattr(m, '__version__'): - return False # pragma: no cover
- required_version = pkg_resources.parse_version(version) - module_version = pkg_resources.parse_version(m.__version__) + required_version = packaging.version.Version(version) + module_version = packaging.version.Version(metadata_version)
if module_version < required_version: warn('Module version {} is lower than requested version {}' @@ -404,36 +397,6 @@ return first_upper(username)
-class Version(pkg_Version): - - """Version from pkg_resouce vendor package. - - This Version provides propreties of vendor package 20.4 shipped with - setuptools 49.4.0. - - .. versionadded:: 6.4 - """ - - def __getattr__(self, name): - """Provides propreties of vendor package 20.4.""" - if name in ('epoch', 'release', 'pre', ): - return getattr(self._version, name) - if name in ('post', 'dev'): - attr = getattr(self._version, name) - return attr[1] if attr else None - if name == 'is_devrelease': - return self.dev is not None - - parts = ('major', 'minor', 'micro') - try: - index = parts.index(name) - except ValueError: - raise AttributeError('{!r} object has to attribute {!r}' - .format(type(self).__name__, name)) from None - release = self.release - return release[index] if len(release) >= index + 1 else 0 - - @total_ordering class MediaWikiVersion:
diff --git a/setup.py b/setup.py index c5da297..f3d0ec8 100755 --- a/setup.py +++ b/setup.py @@ -87,7 +87,9 @@ # ------- setup install_requires ------- # # packages which are mandatory dependencies = [ + 'importlib_metadata ; python_version < "3.8"', 'mwparserfromhell>=0.5.2', + 'packaging', 'requests>=2.21.0', # PEP 440 'setuptools>=48.0.0 ; python_version >= "3.10"', @@ -129,7 +131,6 @@ and is not a developmental release.
:return: pywikibot module version string - :rtype: str """ version = metadata.__version__ if 'sdist' not in sys.argv: @@ -139,7 +140,8 @@ from contextlib import suppress from subprocess import PIPE, run
- from pkg_resources import parse_version, safe_version + from packaging.version import InvalidVersion, Version + try: tags = run(['git', 'tag'], check=True, stdout=PIPE, universal_newlines=True).stdout.splitlines() @@ -155,24 +157,21 @@
last_tag = tags[-1]
- warnings = [] - if parse_version(version) < parse_version('0'): # pragma: no cover - # any version which is not a valid PEP 440 version will be considered - # less than any valid PEP 440 version - warnings.append( - version + ' is not a valid version string following PEP 440.') - elif safe_version(version) != version: # pragma: no cover - warnings.append(f'{version} does not follow PEP 440. Use ' - f'{safe_version(version)} as version string instead.') + warning = '' + try: + vrsn = Version(version) + except InvalidVersion: # pragma: no cover + warning = f'{version} is not a valid version string following PEP 440.' + else: + if last_tag and vrsn <= Version(last_tag): + warning = ( # pragma: no cover + f'New version {version!r} is not higher than last version ' + f'{last_tag!r}.' + )
- if last_tag and parse_version(version) <= parse_version(last_tag): - warnings.append( # pragma: no cover - f'New version {version!r} is not higher than last version ' - f'{last_tag!r}.') - - if warnings: # pragma: no cover + if warning: # pragma: no cover print(__doc__) - print('\n\n'.join(warnings)) + print('\n\n{warning}') sys.exit('\nBuild of distribution package canceled.')
return version