jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/566013 )
Change subject: [deps] require ipaddress for Python 2 ......................................................................
[deps] require ipaddress for Python 2
- ipaddress is backported from Python 3 and will be mandatory with next release. ipaddr is superseeded by ipaddress and no longer maintained - change requirements.txt and setup.py accordingly - move is_IP function from ip subfolder to tools - implement a fallback to the current ip.is_IP implementation until ip subfolder will be removed later - deprecation warnings will be shown for old module - use the new location in api.py, page.py, site.py - remove IPRegexTestCase for regex tests - merge IPAddressModuleTestCase into TestIPBase - split test_T76286_failures into two subtests with expected failures - test_T105443_failures is solves within ipaddress and does not need a separate test from test_ipaddress_module
Bug: T105443 Bug: T243171 Change-Id: Icc094eec294039539e3673584d5b771bc9f1f893 --- M HISTORY.rst M pywikibot/data/api.py M pywikibot/page.py M pywikibot/site.py M pywikibot/tools/__init__.py M pywikibot/tools/ip.py M requirements.txt M setup.py M tests/dry_api_tests.py M tests/tools_ip_tests.py 10 files changed, 82 insertions(+), 100 deletions(-)
Approvals: Dvorapa: Looks good to me, approved jenkins-bot: Verified
diff --git a/HISTORY.rst b/HISTORY.rst index 05fdd57..3bcdd32 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,9 @@ Current release ---------------
+* ipaddress module is required for Python 2 (T243171) +* tools.ip will be dropped in favour of tools.is_IP (T243171) +* tools.ip_regexp is deprecatd for 5 years and will be removed with next release * backports.py will be removed with next release (T244664) * stdnum package is required for ISBN scripts and cosmetic_changes (T132919, T144288, T241141) * preload urllib.quote() with Python 2 (T243710, T222623) diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py index 886997b..8cccf44 100644 --- a/pywikibot/data/api.py +++ b/pywikibot/data/api.py @@ -38,7 +38,7 @@ Error, TimeoutError, InvalidTitle, UnsupportedPage ) from pywikibot.tools import ( - deprecated, itergroup, ip, PY2, PYTHON_VERSION, + deprecated, itergroup, is_IP, PY2, PYTHON_VERSION, getargspec, UnicodeType, remove_last_args ) from pywikibot.tools.formatter import color_format @@ -1253,7 +1253,7 @@ raise Error('API write action attempted without userinfo') assert('name' in self.site._userinfo)
- if ip.is_IP(self.site._userinfo['name']): + if is_IP(self.site._userinfo['name']): raise Error('API write action attempted as IP %r' % self.site._userinfo['name'])
diff --git a/pywikibot/page.py b/pywikibot/page.py index c154adf..46115d0 100644 --- a/pywikibot/page.py +++ b/pywikibot/page.py @@ -50,7 +50,8 @@ first_upper, redirect_func, remove_last_args, UnicodeType, StringTypes ) -from pywikibot.tools.ip import is_IP, ip_regexp +from pywikibot.tools.ip import ip_regexp # deprecated +from pywikibot.tools import is_IP
if not PY2: from html import entities as htmlentitydefs diff --git a/pywikibot/site.py b/pywikibot/site.py index 55351fe..93ffe1a 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -83,7 +83,7 @@ filter_unique, UnicodeType ) -from pywikibot.tools.ip import is_IP +from pywikibot.tools import is_IP
if not PY2: from itertools import zip_longest diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py index 2d9b6d5..a4d2219 100644 --- a/pywikibot/tools/__init__.py +++ b/pywikibot/tools/__init__.py @@ -42,11 +42,16 @@ import queue StringTypes = (str, bytes) UnicodeType = str + from ipaddress import ip_address else: from itertools import izip_longest as zip_longest import Queue as queue # noqa: N813 StringTypes = types.StringTypes UnicodeType = types.UnicodeType + try: + from ipaddress import ip_address + except ImportError: + ip_address = None
try: import bz2 @@ -373,6 +378,33 @@ '%s: %s' % (self.__class__.__name__, self.__doc__))
+def is_IP(IP): # noqa N802, N803 + """Verify the IP address provided is valid. + + No logging is performed. Use ip_address instead to catch errors. + + @param IP: IP address + @type IP: str + @rtype: bool + """ + method = ip_address + if not ip_address: # Python 2 needs ipaddress to be installed + issue_deprecation_warning( + 'ipaddr module or tools.ip.ip_regexp', 'ipaddress module', + warning_class=FutureWarning, since='20200120') + from pywikibot.tools import ip + with suppress_warnings('pywikibot.tools.ip.is_IP is deprecated'): + method = ip.is_IP + + try: + method(IP) + except ValueError: + pass + else: + return True + return False + + def has_module(module, version=None): """Check whether a module can be imported.""" try: @@ -685,7 +717,8 @@ def __getattr__(self, attr): """Issue deprecation warning.""" issue_deprecation_warning( - self._name, self._instead, since=self._since) + self._name, self._instead, warning_class=FutureWarning, + since=self._since) return super(DeprecatedRegex, self).__getattr__(attr)
diff --git a/pywikibot/tools/ip.py b/pywikibot/tools/ip.py index 5a9ea2f..9130251 100644 --- a/pywikibot/tools/ip.py +++ b/pywikibot/tools/ip.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """IP address tools module.""" # -# (C) Pywikibot team, 2014-2019 +# (C) Pywikibot team, 2014-2020 # # Distributed under the terms of the MIT license. # @@ -12,7 +12,8 @@ from distutils.version import StrictVersion from warnings import warn
-from pywikibot.tools import DeprecatedRegex, PY2, UnicodeType +from pywikibot.tools import (DeprecatedRegex, PY2, UnicodeType, + ModuleDeprecationWrapper)
_ipaddress_e = _ipaddr_e = _ipaddr_version = None
@@ -87,7 +88,7 @@ r'(([\dA-F]{1,4}(\4|:\b|$)|\2){2}|' r'(((2[0-4]|1\d|[1-9])?\d|25[0-5]).?\b){4}))\Z', re.IGNORECASE, - 'page.ip_regexp', 'tools.ip.is_IP', since='20150212') + 'page.ip_regexp', 'tools.is_IP', since='20150212')
def is_IP(IP): @@ -105,3 +106,10 @@ return True except ValueError: return False + + +wrapper = ModuleDeprecationWrapper(__name__) +wrapper._add_deprecated_attr('is_IP', + replacement_name='tools.is_IP', + future_warning=True, + since='20200120') diff --git a/requirements.txt b/requirements.txt index 9464a2e..6e9e858 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,15 +18,14 @@ # or # $ awk -F '[#>=]' '{print $1}' requirements.txt | xargs apt-cache search
-# mandatory; see README.conversion.txt +# mandatory; see README-conversion.txt requests>=2.20.1,<2.22.0; python_version == '3.4' requests>=2.20.1; python_version != '3.4' enum34>=1.1.6; python_version < '3' +ipaddress>=1.0.23 ; python_version < '3' # requests security extra requests[security] ; python_full_version > '2.7.6' and python_full_version < '2.7.9'
-ipaddr>=2.1.10 ; python_version < '3' - # OAuth support # mwoauth 0.2.4 is needed because it supports getting identity information # about the user diff --git a/setup.py b/setup.py index f51970e..d3b462c 100644 --- a/setup.py +++ b/setup.py @@ -97,18 +97,11 @@ extra_deps.update(script_deps)
# ------- setup install_requires ------- # +# packages which are mandatory dependencies = ['requests>=2.20.1,<2.22.0; python_version == "3.4"', 'requests>=2.20.1; python_version != "3.4"', - 'enum34>=1.1.6; python_version < "3"'] -# tools.ip does not have a hard dependency on an IP address module, -# as it falls back to using regexes if one is not available. -# The functional backport of py3 ipaddress is acceptable: -# https://pypi.org/project/ipaddress -# However the Debian package python-ipaddr is also supported: -# https://pypi.org/project/ipaddr -# Other backports are likely broken. -# ipaddr 2.1.10+ is distributed with Debian and Fedora. See T105443. -dependencies.append('ipaddr>=2.1.10;python_version<"3"') + 'enum34>=1.1.6; python_version < "3"', + 'ipaddress>=1.0.23; python_version < "3"']
# version.package_version() uses pathlib which is a python 3 library. # pathlib2 is required for python 2.7 diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py index 348a887..a0f0b63 100644 --- a/tests/dry_api_tests.py +++ b/tests/dry_api_tests.py @@ -248,11 +248,10 @@ Request, site=site, parameters={'action': 'edit'})
- # Explicitly using str as the test expects it to be str (without the - # u-prefix) in Python 2 and this module is using unicode_literals - site._userinfo = {'name': str('1.2.3.4'), 'groups': []} + site._userinfo = {'name': '1.2.3.4', 'groups': []}
- self.assertRaisesRegex(pywikibot.Error, " as IP '1.2.3.4'", + # unicode string with "u" is returned with Python 2 + self.assertRaisesRegex(pywikibot.Error, " as IP u?'1.2.3.4'", Request, site=site, parameters={'action': 'edit'})
diff --git a/tests/tools_ip_tests.py b/tests/tools_ip_tests.py index 88cd474..09aff29 100644 --- a/tests/tools_ip_tests.py +++ b/tests/tools_ip_tests.py @@ -7,16 +7,14 @@ # Distributed under the terms of the MIT license. from __future__ import absolute_import, division, unicode_literals
-from distutils.version import StrictVersion - -from pywikibot.tools import ip, PY2, PYTHON_VERSION +from pywikibot.tools import is_IP, PY2, PYTHON_VERSION
from tests import unittest_print -from tests.aspects import unittest, TestCase, DeprecationTestCase +from tests.aspects import unittest, TestCase from tests.utils import expected_failure_if
-class TestIPBase(TestCase): +class IPAddressModuleTestCase(TestCase):
"""Unit test class base for IP matching."""
@@ -25,11 +23,11 @@ def setUp(self): """Set up test.""" self.total = 0 - super(TestIPBase, self).setUp() + super(IPAddressModuleTestCase, self).setUp()
def tearDown(self): """Tear down test.""" - super(TestIPBase, self).tearDown() + super(IPAddressModuleTestCase, self).tearDown() unittest_print('{} subtests done'.format(self.total))
def ipv6test(self, result, ip_address): @@ -38,13 +36,10 @@ with self.subTest(ip_address=ip_address): msg = '"{}" match should be {} - not OK'.format( ip_address, result) if PY2 else None - if result: - self.assertTrue(self._do_ip_test(ip_address), msg) - else: - self.assertFalse(self._do_ip_test(ip_address), msg) + self.assertEqual(result, is_IP(ip_address), msg)
- def _run_tests(self): - """Test various IP.""" + def test_ipaddress_module(self): + """Test ipaddress module.""" # test from http://download.dartware.com/thirdparty/test-ipv6-regex.pl self.ipv6test(False, '') # empty string self.ipv6test(True, '::1') # loopback, compressed, non-routable @@ -635,82 +630,33 @@ self.ipv6test(True, 'a:b:c:d:e:f:0::') self.ipv6test(False, "':10.0.0.1")
- def _test_T76286_failures(self): + # Known bugs with ipaddr v2.1.10 but works with ipaddress + self.ipv6test(False, '02001:0000:1234:0000:0000:C1C0:ABCD:0876') + self.ipv6test(False, '2001:0000:1234:0000:00001:C1C0:ABCD:0876') + + @unittest.expectedFailure + def test_T76286a_failures(self): """Test known bugs in the ipaddress module.""" # The following fail with the ipaddress module. See T76286 self.ipv6test(False, '1111:2222:3333:4444:5555:6666:00.00.00.00') + + @unittest.expectedFailure + def test_T76286b_failures(self): + """Test known bugs in the ipaddress module.""" self.ipv6test(False, '1111:2222:3333:4444:5555:6666:000.000.000.000')
- def _test_T105443_failures(self): - """Test known bugs with ipaddr v2.1.10.""" - # extra 0 not allowed! - self.ipv6test(False, '02001:0000:1234:0000:0000:C1C0:ABCD:0876') - # extra 0 not allowed! - self.ipv6test(False, '2001:0000:1234:0000:00001:C1C0:ABCD:0876') - - def _test_T240060_failures(self): + @expected_failure_if(PYTHON_VERSION >= (3, 8)) + def test_T240060_failures(self): """Test known deviated behaviour in Python 3.8.""" # Testing IPv4 addresses represented as dotted-quads # Leading zero's in IPv4 addresses not allowed: some systems treat the # leading "0" in ".086" as the start of an octal number # Update: The BNF in RFC-3986 explicitly defines the dec-octet # (for IPv4 addresses) not to have a leading zero - self.ipv6test(PYTHON_VERSION >= (3, 8), + self.ipv6test(False, 'fe80:0000:0000:0000:0204:61ff:254.157.241.086')
-class IPRegexTestCase(TestIPBase, DeprecationTestCase): - - """Test IP regex.""" - - def _do_ip_test(self, address): - return bool(ip.ip_regexp.match(address)) - - def test_regex(self): - """Test IP regex.""" - self._run_tests() - self._test_T76286_failures() - self._test_T105443_failures() - self.assertDeprecationParts('page.ip_regexp', 'tools.ip.is_IP') - self.assertLength(self.deprecation_messages, self.total) - - -class IPAddressModuleTestCase(TestIPBase): - - """Test ipaddress module.""" - - def _do_ip_test(self, address): - return ip.is_IP(address) - - @classmethod - def setUpClass(cls): - """Check ipaddress module is available.""" - if ip.ip_address.__name__ == 'ip_address_fake': - raise unittest.SkipTest('module ipaddress not available') - - super(IPAddressModuleTestCase, cls).setUpClass() - - def test_ipaddress_module(self): - """Test ipaddress module.""" - self._run_tests() - - @expected_failure_if(ip.ip_address.__module__ == 'ipaddress' - or ip.ip_address.__name__ == 'ip_address_patched') - def test_T76286_failures(self): - """Test known bugs in the ipaddress module.""" - self._test_T76286_failures() - - @expected_failure_if(ip.ip_address.__module__ == 'ipaddr' - and ip._ipaddr_version == StrictVersion('2.1.10')) - def test_T105443_failures(self): - """Test known bugs in the ipaddr module.""" - self._test_T105443_failures() - - def test_T240060_failures(self): - """Test known bugs in the ipaddr module.""" - self._test_T240060_failures() - - if __name__ == '__main__': # pragma: no cover try: unittest.main()
pywikibot-commits@lists.wikimedia.org