jenkins-bot submitted this change.

View Change


Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
[IMPR] Revise Family class

- remove obsolete.setter which is only used for tests
- move interwiki_replacements and interwiki_removals class
properties from WikimediaFamily to Family class to have
a unique result for all families
- add empty closed_wikis, removed_wikis and code_aliases to
Family class which are used by interwiki_replacements and
interwiki_removals; remove closed_wikis and removed_wikis
from WikimediaFamily.
- simplify SingleSiteFamily because closed_wikis is always present
- codes must be a List not a Tuple in Family classes because SubdomainFamily
modifies codes; update wikihow and wowwiki families
- update tests accordingly

This changes enables to change the interface for removed or closed sites
with wowwiki and others.

Bug: T334834
Change-Id: Ib22cbb964670471a60b47d89734fb4d63cea5710
---
M pywikibot/families/wikihow_family.py
M pywikibot/family.py
M pywikibot/families/wowwiki_family.py
M tests/family_tests.py
4 files changed, 95 insertions(+), 61 deletions(-)

diff --git a/pywikibot/families/wikihow_family.py b/pywikibot/families/wikihow_family.py
index d72fe38..d49b642 100644
--- a/pywikibot/families/wikihow_family.py
+++ b/pywikibot/families/wikihow_family.py
@@ -3,7 +3,7 @@
.. versionadded:: 3.0
"""
#
-# (C) Pywikibot team, 2020-2022
+# (C) Pywikibot team, 2020-2023
#
# Distributed under the terms of the MIT license.
#
@@ -21,10 +21,10 @@
name = 'wikihow'
domain = 'wikihow.com'

- codes = (
+ codes = [
'ar', 'cs', 'de', 'en', 'es', 'fr', 'hi', 'id', 'it', 'ja', 'ko', 'nl',
'pt', 'ru', 'th', 'tr', 'vi', 'zh',
- )
+ ]

removed_wikis = ['ca', 'cy', 'fa', 'he', 'pl', 'ur']

diff --git a/pywikibot/families/wowwiki_family.py b/pywikibot/families/wowwiki_family.py
index b683c71..fdb5871 100644
--- a/pywikibot/families/wowwiki_family.py
+++ b/pywikibot/families/wowwiki_family.py
@@ -15,11 +15,11 @@
name = 'wowwiki'
domain = 'wowwiki.fandom.com'

- codes = (
+ codes = [
'ar', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'fa', 'fi', 'fr', 'he',
'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ru', 'uk',
'zh', 'zh-tw',
- )
+ ]

removed_wikis = ['is', 'hr', 'lt', 'lv', 'ro', 'sk', 'sr', 'sv', 'tr']

diff --git a/pywikibot/family.py b/pywikibot/family.py
index 9966afe..1d9d0b8 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -1,6 +1,6 @@
"""Objects representing MediaWiki families."""
#
-# (C) Pywikibot team, 2004-2022
+# (C) Pywikibot team, 2004-2023
#
# Distributed under the terms of the MIT license.
#
@@ -18,9 +18,11 @@

import pywikibot
from pywikibot import config
-from pywikibot.backports import ( # skipcq: PY-W2000
+from pywikibot.backports import (
Dict,
+ FrozenSet,
List,
+ Mapping,
Set,
Tuple,
removesuffix,
@@ -45,6 +47,8 @@
.. versionchanged:: 8.0
``alphabetic``, ``alphabetic_revised`` and ``fyinterwiki``
attributes where removed.
+ .. versionchanged:: 8.2
+ :attr:`obsolete` setter was removed.
"""

def __new__(cls):
@@ -82,6 +86,19 @@

name = None

+ #: Not open for edits; stewards can still edit.
+ closed_wikis: List[str] = []
+
+ #: Completely removed sites
+ removed_wikis: List[str] = []
+
+ code_aliases: Dict[str, str] = {}
+ """Code mappings which are only an alias, and there is no 'old' wiki.
+
+ For all except 'nl_nds', subdomains do exist as a redirect, but that
+ should not be relied upon.
+ """
+
langs: Dict[str, str] = {}

# A list of category redirect template names in different languages
@@ -161,15 +178,6 @@
# family.
interwiki_forward = None

- # Which language codes no longer exist and by which language code
- # should they be replaced. If for example the language with code xx:
- # now should get code yy:, add {'xx':'yy'} to obsolete.
- interwiki_replacements: Dict[str, str] = {}
-
- # Codes that should be removed, usually because the site has been
- # taken down.
- interwiki_removals: List[str] = []
-
# Language codes of the largest wikis. They should be roughly sorted
# by size.
languages_by_size: List[str] = []
@@ -528,7 +536,7 @@
"""Return the path to title using index.php with redirects disabled."""
return f'{self.path(code)}?title={title}&redirect=no'

- def interface(self, code) -> str:
+ def interface(self, code: str) -> str:
"""Return interface to use for code."""
if code in self.interwiki_removals:
if code in self.codes:
@@ -691,16 +699,6 @@
data.update(self.interwiki_replacements)
return types.MappingProxyType(data)

- @obsolete.setter
- def obsolete(self, data) -> None:
- """Split obsolete dict into constituent parts."""
- self.interwiki_removals[:] = [old for (old, new) in data.items()
- if new is None]
- self.interwiki_replacements.clear()
- self.interwiki_replacements.update((old, new)
- for (old, new) in data.items()
- if new is not None)
-
@classproperty
def domains(cls) -> Set[str]:
"""
@@ -719,6 +717,32 @@
"""
return set(cls.langs.keys())

+ @classproperty
+ def interwiki_replacements(cls) -> Mapping[str, str]:
+ """Return an interwiki code replacement mapping.
+
+ Which language codes no longer exist and by which language code
+ should they be replaced. If for example the language with code
+ xx: now should get code yy:, add {'xx':'yy'} to
+ :attr:`code_aliases`.
+
+ .. versionchanged:: 8.2
+ changed from dict to invariant mapping.
+ """
+ return types.MappingProxyType(cls.code_aliases)
+
+ @classproperty
+ def interwiki_removals(cls) -> FrozenSet[str]:
+ """Return a list of interwiki codes to be removed from wiki pages.
+
+ Codes that should be removed, usually because the site has been
+ taken down.
+
+ .. versionchanged:: 8.2
+ changed from list to invariant frozenset.
+ """
+ return frozenset(cls.removed_wikis + cls.closed_wikis)
+

class SingleSiteFamily(Family):

@@ -761,16 +785,13 @@

if hasattr(cls, 'test_codes'):
codes += cls.test_codes
- if hasattr(cls, 'closed_wikis'):
- codes += cls.closed_wikis
+
+ codes += cls.closed_wikis

# shortcut this classproperty
- cls.langs = {code: f'{code}.{cls.domain}'
- for code in codes}
-
- if hasattr(cls, 'code_aliases'):
- cls.langs.update({alias: f'{code}.{cls.domain}'
- for alias, code in cls.code_aliases.items()})
+ cls.langs = {code: f'{code}.{cls.domain}' for code in codes}
+ cls.langs.update({alias: f'{code}.{cls.domain}'
+ for alias, code in cls.code_aliases.items()})

return cls.langs

@@ -934,11 +955,6 @@
'be-x-old': 'be-tarask',
}

- # Not open for edits; stewards can still edit.
- closed_wikis: List[str] = []
- # Completely removed
- removed_wikis: List[str] = []
-
# WikimediaFamily uses Wikibase for the category name containing
# disambiguation pages for the various languages. We need the
# Wikibase code and item number:
@@ -960,16 +976,6 @@
raise NotImplementedError(
f"Family {cls.name} needs to define property 'domain'")

- @classproperty
- def interwiki_removals(cls):
- """Return a list of interwiki codes to be removed from wiki pages."""
- return frozenset(cls.removed_wikis + cls.closed_wikis)
-
- @classproperty
- def interwiki_replacements(cls):
- """Return an interwiki code replacement mapping."""
- return types.MappingProxyType(cls.code_aliases)
-
def shared_image_repository(self, code):
"""Return Wikimedia Commons as the shared image repository."""
return ('commons', 'commons')
diff --git a/tests/family_tests.py b/tests/family_tests.py
index 2e65095..3ca109e 100755
--- a/tests/family_tests.py
+++ b/tests/family_tests.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Tests for the family module."""
#
-# (C) Pywikibot team, 2014-2022
+# (C) Pywikibot team, 2014-2023
#
# Distributed under the terms of the MIT license.
#
@@ -103,6 +103,8 @@
family = Family.load('wikipedia')
self.assertIsInstance(family.obsolete, Mapping)
# redirected code (see site tests test_alias_code_site)
+ self.assertEqual(family.code_aliases['dk'], 'da')
+ self.assertEqual(family.interwiki_replacements['dk'], 'da')
self.assertEqual(family.obsolete['dk'], 'da')
# closed/locked site (see site tests test_locked_site)
self.assertIsNone(family.obsolete['mh'])
@@ -110,19 +112,21 @@
self.assertIsNone(family.obsolete['ru-sib'])
self.assertIn('dk', family.interwiki_replacements)

- def test_set_obsolete(self):
- """Test obsolete can be set."""
+ def test_obsolete_from_attributes(self):
+ """Test obsolete property for given class attributes."""
# Construct a temporary family and instantiate it
family = type('TempFamily', (Family,), {})()

self.assertEqual(family.obsolete, {})
self.assertEqual(family.interwiki_replacements, {})
- self.assertEqual(family.interwiki_removals, [])
+ self.assertEqual(family.interwiki_removals, frozenset())

- family.obsolete = {'a': 'b', 'c': None}
+ # Construct a temporary family with other attributes and instantiate it
+ family = type('TempFamily', (Family,),
+ {'code_aliases': {'a': 'b'}, 'closed_wikis': ['c']})()
self.assertEqual(family.obsolete, {'a': 'b', 'c': None})
self.assertEqual(family.interwiki_replacements, {'a': 'b'})
- self.assertEqual(family.interwiki_removals, ['c'])
+ self.assertEqual(family.interwiki_removals, frozenset('c'))

def test_obsolete_readonly(self):
"""Test obsolete result not updatable."""
@@ -137,12 +141,10 @@
"'mappingproxy' object does not support item assignment"):
family.obsolete['a'] = 'b'

- def test_WikimediaFamily_obsolete_readonly(self):
- """Test WikimediaFamily obsolete is readonly."""
- family = Family.load('wikipedia')
with self.assertRaisesRegex(
- TypeError,
- "'frozenset' object does not support item assignment"):
+ AttributeError,
+ "property 'obsolete' of 'Family' object has no setter|"
+ "can't set attribute"):
family.obsolete = {'a': 'b', 'c': None}



To view, visit change 909314. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ib22cbb964670471a60b47d89734fb4d63cea5710
Gerrit-Change-Number: 909314
Gerrit-PatchSet: 9
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged