Xqt has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1307599?usp=email )
Change subject: Rename TimeoutError to ApiTimeoutError, keep old name as deprecated alias ......................................................................
Rename TimeoutError to ApiTimeoutError, keep old name as deprecated alias
Bug: T431174 Change-Id: Ie36effbc761510c1b1a9efffe0d1dbd2087a652b --- M ROADMAP.rst M pywikibot/data/__init__.py M pywikibot/exceptions.py M pywikibot/page/_toolforge.py M tests/__init__.py A tests/exceptions_tests.py M tests/page_tests.py M tests/site_generators_tests.py M tests/sparql_tests.py 9 files changed, 105 insertions(+), 18 deletions(-)
Approvals: jenkins-bot: Verified Xqt: Looks good to me, approved
diff --git a/ROADMAP.rst b/ROADMAP.rst index 7d1d12e..f7d7322 100644 --- a/ROADMAP.rst +++ b/ROADMAP.rst @@ -1,6 +1,7 @@ Release 11.5 ============
+* Rename :exc:`exceptions.TimeoutError` exception to :class:`exceptions.ApiTimeoutError` to avoid collision with Python's built-in exception (:phab:`T431174`) * Drop :mod:`config` ``textfile_encoding`` variable and use 'utf-8' directly instead (:phab:`T430454`) * Use pyclean package with :mod:`make_dist` script. * Fix redirected link for :class:`pagegenerators.PetScanPageGenerator` @@ -107,6 +108,7 @@ Pending removal in Pywikibot 14 -------------------------------
+* 11.5.0: :exc:`exceptions.TimeoutError` is deprecated in favour of :class:`exceptions.ApiTimeoutError` * 11.5.0: :mod:`config` ``textfile_encoding`` variable will be removed; use 'utf-8' directly instead (:phab:`T430454`) * 11.4.0: The signature of :meth:`APISite.allusers() diff --git a/pywikibot/data/__init__.py b/pywikibot/data/__init__.py index aa58b00..19270cc 100644 --- a/pywikibot/data/__init__.py +++ b/pywikibot/data/__init__.py @@ -44,7 +44,7 @@ self.current_retries += 1
if self.current_retries > self.max_retries: - raise pywikibot.exceptions.TimeoutError( + raise pywikibot.exceptions.ApiTimeoutError( 'Maximum retries attempted without success.')
# double the next wait, but do not exceed config.retry_max seconds diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py index 7c00ac5..7aa1831 100644 --- a/pywikibot/exceptions.py +++ b/pywikibot/exceptions.py @@ -54,7 +54,7 @@ +-- SiteDefinitionError | +-- UnknownFamilyError | +-- UnknownSiteError - +-- TimeoutError + +-- ApiTimeoutError | +-- MaxlagTimeoutError +-- TranslationError +-- UserRightsError @@ -143,7 +143,7 @@ - CoordinateGlobeUnknownError: globe is not implemented yet. - EntityTypeUnknownError: entity type is not available on the site.
-TimeoutError: request failed with a timeout +ApiTimeoutError: request failed with a timeout
- MaxlagTimeoutError: request failed with a maxlag timeout
@@ -182,7 +182,10 @@ from typing import Any
import pywikibot -from pywikibot.tools._deprecate import _NotImplementedWarning +from pywikibot.tools._deprecate import ( + ModuleDeprecationWrapper, + _NotImplementedWarning, +)
class NotImplementedWarning(_NotImplementedWarning): @@ -720,12 +723,12 @@ """The requested entity type is not recognised on this site."""
-class TimeoutError(Error): +class ApiTimeoutError(Error):
"""Request failed with a timeout error."""
-class MaxlagTimeoutError(TimeoutError): +class MaxlagTimeoutError(ApiTimeoutError):
"""Request failed with a maxlag timeout error."""
@@ -733,3 +736,11 @@ class ApiNotAvailableError(Error):
"""API is not available, e.g. due to a network error or configuration.""" + + +wrapper = ModuleDeprecationWrapper(__name__) +wrapper.add_deprecated_attr( + 'TimeoutError', + replacement=ApiTimeoutError, + since='11.5.0', +) diff --git a/pywikibot/page/_toolforge.py b/pywikibot/page/_toolforge.py index 84437b0..1e2426e 100644 --- a/pywikibot/page/_toolforge.py +++ b/pywikibot/page/_toolforge.py @@ -72,7 +72,7 @@ :raise NotImplementedError: unsupported site or unsupported namespace. :raise NoPageError: The page does not exist. - :raise TimeoutError: WikiHistory timeout + :raise ApiTimeoutError: WikiHistory timeout """ return collections.Counter( {user: int(cnt) for user, (_, cnt) in self.authorship(5).items()}) @@ -166,7 +166,7 @@ :raise NotImplementedError: unsupported site or unsupported namespace. :raise NoPageError: The page does not exist. - :raise TimeoutError: WikiHistory timeout + :raise ApiTimeoutError: WikiHistory timeout """ if n and n > 5: warn('Only the first 5 authors can be given.', stacklevel=2) @@ -190,7 +190,7 @@
pywikibot.sleep(pywikibot.config.retry_wait) else: - raise pywikibot.exceptions.TimeoutError('WikiHistory Timeout') + raise pywikibot.exceptions.ApiTimeoutError('WikiHistory Timeout')
length = len(self.text) result: list[list[str]] = [] diff --git a/tests/__init__.py b/tests/__init__.py index 7c1fb7d..851a402 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -81,6 +81,7 @@ 'echo', 'edit', 'edit_failure', + 'exceptions', 'eventstreams', 'family', 'file', diff --git a/tests/exceptions_tests.py b/tests/exceptions_tests.py new file mode 100755 index 0000000..96d1925 --- /dev/null +++ b/tests/exceptions_tests.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# (C) Pywikibot team, 2026 +# +# Distributed under the terms of the MIT license. +# +"""Tests for the exceptions module.""" +from __future__ import annotations + +import unittest +from contextlib import suppress + +import pywikibot +from pywikibot.exceptions import ApiTimeoutError, Error, MaxlagTimeoutError +from tests.aspects import DeprecationTestCase, TestCase + + +class TestApiTimeoutError(TestCase): + + """Test ApiTimeoutError exceptions.""" + + net = False + + def test_hierarchy(self) -> None: + """Test inheritance hierarchy.""" + self.assertTrue(issubclass(ApiTimeoutError, Error)) + self.assertTrue(issubclass(MaxlagTimeoutError, ApiTimeoutError)) + + def test_raise_catch(self) -> None: + """Test raising and catching ApiTimeoutError.""" + with self.assertRaises(ApiTimeoutError): + raise ApiTimeoutError('Test timeout') + + with self.assertRaises(ApiTimeoutError): + raise MaxlagTimeoutError('Test maxlag timeout') + + +class TestTimeoutErrorDeprecation(DeprecationTestCase): + + """Test deprecation of TimeoutError alias.""" + + net = False + + def test_deprecation(self) -> None: + """Test that accessing TimeoutError triggers a deprecation warning.""" + # Accessing the deprecated attribute + exc_class = pywikibot.exceptions.TimeoutError + self.assertIs(exc_class, ApiTimeoutError) + self.assertOneDeprecationParts( + 'pywikibot.exceptions.TimeoutError', + 'pywikibot.exceptions.ApiTimeoutError' + ) + + def test_raise_catch_deprecated(self) -> None: + """Test raising and catching using the deprecated alias.""" + # Reset any messages from previous tests + self._reset_messages() + + # Catch ApiTimeoutError using the TimeoutError alias + try: + raise ApiTimeoutError('Test raise') + except pywikibot.exceptions.TimeoutError: + pass + + self.assertOneDeprecationParts( + 'pywikibot.exceptions.TimeoutError', + 'pywikibot.exceptions.ApiTimeoutError' + ) + + +if __name__ == '__main__': + with suppress(SystemExit): + unittest.main() diff --git a/tests/page_tests.py b/tests/page_tests.py index 8309042..2aef677 100755 --- a/tests/page_tests.py +++ b/tests/page_tests.py @@ -19,13 +19,13 @@ from pywikibot import config from pywikibot.exceptions import ( APIError, + ApiTimeoutError, Error, InvalidTitleError, IsNotRedirectPageError, IsRedirectPageError, NoPageError, SectionError, - TimeoutError, UnknownExtensionError, ) from pywikibot.tools import suppress_warnings @@ -523,7 +523,7 @@ for p in mainpage.backlinks(follow_redirects=False, total=10): self.assertIsInstance(p, pywikibot.Page)
- with skipping(TimeoutError): + with skipping(ApiTimeoutError): for p in mainpage.embeddedin(total=10): self.assertIsInstance(p, pywikibot.Page)
diff --git a/tests/site_generators_tests.py b/tests/site_generators_tests.py index 2e8676e..4f4feb3 100755 --- a/tests/site_generators_tests.py +++ b/tests/site_generators_tests.py @@ -15,11 +15,11 @@ from pywikibot.data import api from pywikibot.exceptions import ( APIError, + ApiTimeoutError, Error, HiddenKeyError, InvalidTitleError, NoPageError, - TimeoutError, ) from pywikibot.tools import suppress_warnings from tests import WARN_SITE_CODE, unittest_print @@ -65,7 +65,7 @@ """Test Site.pagereferences.""" # pagereferences includes both backlinks and embeddedin backlinks = set(self.site.pagebacklinks(self.mainpage, namespaces=[0])) - with skipping(TimeoutError): + with skipping(ApiTimeoutError): embedded = set(self.site.page_embeddedin(self.mainpage, namespaces=[0])) refs = set(self.site.pagereferences(self.mainpage, namespaces=[0])) @@ -105,7 +105,7 @@
def test_embeddedin(self) -> None: """Test Site.page_embeddedin.""" - with skipping(TimeoutError): + with skipping(ApiTimeoutError): embedded_ns_0 = set(self.site.page_embeddedin( self.mainpage, namespaces=[0])) embedded_ns_0_2 = set(self.site.page_embeddedin( diff --git a/tests/sparql_tests.py b/tests/sparql_tests.py index 6ae2d13..ffb985f 100755 --- a/tests/sparql_tests.py +++ b/tests/sparql_tests.py @@ -109,7 +109,7 @@ """Test SELECT query.""" mock_method.return_value = Container( SQL_RESPONSE_CONTAINER % f'{ITEM_Q498787}, {ITEM_Q677525}') - with skipping(pywikibot.exceptions.TimeoutError): + with skipping(pywikibot.exceptions.ApiTimeoutError): q = sparql.SparqlQuery() res = q.select('SELECT * WHERE { ?x ?y ?z }') self.assertIsInstance(res, list, 'Result is not a list') @@ -131,7 +131,7 @@ """Test SELECT query with full data.""" mock_method.return_value = Container( SQL_RESPONSE_CONTAINER % f'{ITEM_Q498787}, {ITEM_Q677525}') - with skipping(pywikibot.exceptions.TimeoutError): + with skipping(pywikibot.exceptions.ApiTimeoutError): q = sparql.SparqlQuery() res = q.select('SELECT * WHERE { ?x ?y ?z }', full_data=True) self.assertIsInstance(res, list, 'Result is not a list') @@ -162,7 +162,7 @@ SQL_RESPONSE_CONTAINER % (f'{ITEM_Q498787}, {ITEM_Q677525}, ' f'{ITEM_Q677525}') ) - with skipping(pywikibot.exceptions.TimeoutError): + with skipping(pywikibot.exceptions.ApiTimeoutError): q = sparql.SparqlQuery() res = q.get_items('SELECT * WHERE { ?x ?y ?z }', 'cat') self.assertEqual(res, {'Q498787', 'Q677525'}) @@ -174,7 +174,7 @@ def testQueryAsk(self, mock_method) -> None: """Test ASK query.""" mock_method.return_value = Container(RESPONSE_TRUE) - with skipping(pywikibot.exceptions.TimeoutError): + with skipping(pywikibot.exceptions.ApiTimeoutError): q = sparql.SparqlQuery()
res = q.ask('ASK { ?x ?y ?z }')
pywikibot-commits@lists.wikimedia.org