jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/679612 )
Change subject: [IMPR] Remove distutils ......................................................................
[IMPR] Remove distutils
Python 3.10 deprecated distutils and it will be removed in 3.12. They advise that we use packaging instead...
https://docs.python.org/3.10/whatsnew/3.10.html#distutils-deprecated
We mostly used distutils for version parsing, for which packaging provides a simple replacement...
https://stackoverflow.com/a/11887885 https://packaging.pypa.io/en/latest/version.html#packaging.version.parse
Setuptools contains a copy of packaging under 'pkg_resources.extern.packaging' and pkg_resources.parse_version() is effectively an alias of packaging.version.parse(). Since we already have a depency on Setuptools this uses its copy.
As for get_python_lib() sysconfig can provide the same...
import distutils.sysconfig distutils.sysconfig.get_python_lib(standard_lib=True)
'/usr/lib/python3.8'
import sysconfig sysconfig.get_paths()['stdlib']
'/usr/lib/python3.8'
Bug: T274696 Change-Id: I6245895d4249d32cfae34043eed489cc94ddea2e --- M pywikibot/comms/eventstreams.py M pywikibot/data/mysql.py M pywikibot/tools/__init__.py M pywikibot/version.py M tests/tools_tests.py 5 files changed, 76 insertions(+), 24 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/comms/eventstreams.py b/pywikibot/comms/eventstreams.py index 662598a..6964089 100644 --- a/pywikibot/comms/eventstreams.py +++ b/pywikibot/comms/eventstreams.py @@ -12,11 +12,11 @@ # # Distributed under the terms of the MIT license. # -from distutils.version import LooseVersion -from functools import partial import json import socket
+from functools import partial +from pkg_resources import parse_version from typing import Optional
from requests import __version__ as requests_version @@ -31,7 +31,7 @@ from pywikibot import config, debug, Timestamp, Site, warning from pywikibot.tools import deprecated_args
-if LooseVersion(requests_version) < LooseVersion('2.20.1'): +if parse_version(requests_version) < parse_version('2.20.1'): raise ImportError( 'requests >= 2.20.1 is required for EventStreams;\n' "install it with 'pip install "requests>=2.20.1"'\n") diff --git a/pywikibot/data/mysql.py b/pywikibot/data/mysql.py index 9ab934b..f3caa95 100644 --- a/pywikibot/data/mysql.py +++ b/pywikibot/data/mysql.py @@ -4,7 +4,8 @@ # # Distributed under the terms of the MIT license. # -from distutils.version import LooseVersion +import pkg_resources + from typing import Optional
import pywikibot @@ -62,7 +63,10 @@ charset='utf8', defer_connect=query == 'test', # for tests **credentials) - if LooseVersion(pymysql.__version__) < LooseVersion('1.0.0'): + + pymysql_version = pkg_resources.parse_version(pymysql.__version__) + + if pymysql_version < pkg_resources.parse_version('1.0.0'): from contextlib import closing connection = closing(connection)
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py index f891c09..77a5494 100644 --- a/pywikibot/tools/__init__.py +++ b/pywikibot/tools/__init__.py @@ -10,6 +10,7 @@ import inspect import itertools import os +import pkg_resources import queue import re import stat @@ -23,7 +24,6 @@ from collections import defaultdict from contextlib import suppress from datetime import datetime -from distutils.version import LooseVersion, Version from functools import wraps from importlib import import_module from inspect import getfullargspec @@ -78,24 +78,25 @@
def has_module(module, version=None): - """Check whether a module can be imported.""" + """Check if a module can be imported.""" try: m = import_module(module) except ImportError: - pass + return False else: - if version is None: - return True - try: - module_version = LooseVersion(m.__version__) - except AttributeError: - pass - else: - if module_version >= LooseVersion(version): - return True - warn('Module version {} is lower than requested version {}' - .format(module_version, version), ImportWarning) - return False + if version: + if not hasattr(m, '__version__'): + return False + + required_version = pkg_resources.parse_version(version) + module_version = pkg_resources.parse_version(m.__version__) + + if module_version < required_version: + warn('Module version {} is lower than requested version {}' + .format(module_version, required_version), ImportWarning) + return False + + return True
def empty_iterator(): @@ -501,7 +502,7 @@ return first_upper(username)
-class MediaWikiVersion(Version): +class MediaWikiVersion:
""" Version object to allow comparing 'wmf' versions with normal ones. @@ -526,6 +527,15 @@ MEDIAWIKI_VERSION = re.compile( r'(\d+(?:.\d+)+)(-?wmf.?(\d+)|alpha|beta(\d+)|-?rc.?(\d+)|.*)?$')
+ def __init__(self, version_str): + """ + Initializer. + + @param version_str: version to parse + @type version: str + """ + self.parse(version_str) + @classmethod def from_generator(cls, generator): """Create instance using the generator string.""" @@ -583,6 +593,21 @@ return -1 return 0
+ def __eq__(self, other): + return self._cmp(other) == 0 + + def __lt__(self, other): + return self._cmp(other) < 0 + + def __le__(self, other): + return self._cmp(other) <= 0 + + def __gt__(self, other): + return self._cmp(other) > 0 + + def __ge__(self, other): + return self._cmp(other) >= 0 +
class ThreadedGenerator(threading.Thread):
diff --git a/pywikibot/version.py b/pywikibot/version.py index 20242f5..a71772b 100644 --- a/pywikibot/version.py +++ b/pywikibot/version.py @@ -11,11 +11,11 @@ import socket import subprocess import sys +import sysconfig import time import xml.dom.minidom
from contextlib import closing, suppress -from distutils.sysconfig import get_python_lib from importlib import import_module from io import BytesIO from typing import Optional @@ -415,7 +415,7 @@ if not modules: modules = sys.modules.keys()
- std_lib_dir = pathlib.Path(get_python_lib(standard_lib=True)) + std_lib_dir = pathlib.Path(sysconfig.get_paths()['stdlib'])
root_packages = {key.split('.')[0] for key in modules}
diff --git a/tests/tools_tests.py b/tests/tools_tests.py index ea0c7a3..cda8191 100644 --- a/tests/tools_tests.py +++ b/tests/tools_tests.py @@ -16,7 +16,7 @@ from importlib import import_module
from pywikibot import tools -from pywikibot.tools import classproperty +from pywikibot.tools import classproperty, has_module, suppress_warnings
from tests import join_xml_data_path, mock from tests.aspects import require_modules, TestCase @@ -742,6 +742,29 @@ self.assertEqual(result, 'HelloWorld')
+class TestHasModule(TestCase): + + """Unit test class for has_module.""" + + net = False + + def test_when_present(self): + """Test when the module is available.""" + self.assertTrue(has_module('setuptools')) + self.assertTrue(has_module('setuptools', '1.0')) + + def test_when_missing(self): + """Test when the module is unavailable.""" + self.assertFalse(has_module('no-such-module')) + + @suppress_warnings( + r'^Module version .* is lower than requested version 99999$', + ImportWarning) + def test_when_insufficient_version(self): + """Test when the module is older than what we need.""" + self.assertFalse(has_module('setuptools', '99999')) + + if __name__ == '__main__': # pragma: no cover with suppress(SystemExit): unittest.main()
pywikibot-commits@lists.wikimedia.org