jenkins-bot has submitted this change and it was merged.
Change subject: Family defined Site interface ......................................................................
Family defined Site interface
Allow Family class to determine the appropriate Site class to be used.
It is usable for supporting extensions, using Site classes composed of several APISite subclasses.
It can also be used for the special case of Sites which are for a code that is no longer online.
The Family.from_url cache now stores the Family object instead of the family name, to reduce the number of Family.load calls.
Change-Id: Ia9f141b9e9f493aa2b1c42afcf4daa8049656c10 --- M pywikibot/__init__.py M pywikibot/families/wikidata_family.py M pywikibot/family.py M pywikibot/site.py M tests/aspects.py M tests/site_tests.py M tests/utils.py 7 files changed, 57 insertions(+), 17 deletions(-)
Approvals: XZise: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index 290a925..b75fa13 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -567,7 +567,7 @@ family = pywikibot.family.Family.load(fam) code = family.from_url(url) if code is not None: - matched_sites += [(code, fam)] + matched_sites += [(code, family)]
if matched_sites: if len(matched_sites) > 1: @@ -590,7 +590,11 @@ # Fallback to config defaults code = code or config.mylang fam = fam or config.family - interface = interface or config.site_interface + + if not isinstance(fam, pywikibot.family.Family): + fam = pywikibot.family.Family.load(fam) + + interface = interface or fam.interface(code)
# config.usernames is initialised with a dict for each family name family_name = str(fam) diff --git a/pywikibot/families/wikidata_family.py b/pywikibot/families/wikidata_family.py index b7a2786..2177b90 100644 --- a/pywikibot/families/wikidata_family.py +++ b/pywikibot/families/wikidata_family.py @@ -37,6 +37,10 @@ 'wikidata': ('wikidata', 'test') })
+ def interface(self, code): + """Return 'DataSite'.""" + return 'DataSite' + def shared_data_repository(self, code, transcluded=False): """ Indicate Wikidata is both a repository and its own client. diff --git a/pywikibot/family.py b/pywikibot/family.py index 72753f0..921b7a6 100644 --- a/pywikibot/family.py +++ b/pywikibot/family.py @@ -1098,6 +1098,20 @@ def nice_get_address(self, code, title): return '%s%s' % (self.nicepath(code), title)
+ def interface(self, code): + """ + Return interface to use for code. + + @rtype: str or subclass of BaseSite + """ + if code in self.interwiki_removals: + if code in self.codes: + pywikibot.warn('Interwiki removal %s is in %s codes' + % (code, self)) + return 'RemovedSite' + + return config.site_interface + # List of codes which aren't returned by from_url; True returns None always _ignore_from_url = []
diff --git a/pywikibot/site.py b/pywikibot/site.py index 98de171..2d5bfc0 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -900,7 +900,8 @@
def __repr__(self): """Return internal representation.""" - return 'Site("%s", "%s")' % (self.code, self.family.name) + return '{0}("{1}", "{2}")'.format( + self.__class__.__name__, self.code, self.family)
def __hash__(self): """Return hashable key.""" @@ -1729,6 +1730,15 @@ def __repr__(self): """Return a representation of the internal tokens dictionary.""" return self._tokens.__repr__() + + +class RemovedSite(BaseSite): + + """Site removed from a family.""" + + def __init__(self, code, fam, user=None, sysop=None): + """Constructor.""" + super(RemovedSite, self).__init__(code, fam, user, sysop)
class NonMWAPISite(BaseSite): @@ -6450,10 +6460,6 @@ f.__doc__ = method.__doc__ return f return super(APISite, self).__getattr__(attr) - - def __repr__(self): - """Return internal representation.""" - return 'DataSite("%s", "%s")' % (self.code, self.family.name)
@deprecated("pywikibot.PropertyPage") def _get_propertyitem(self, props, source, **params): diff --git a/tests/aspects.py b/tests/aspects.py index fa0e486..0503077 100644 --- a/tests/aspects.py +++ b/tests/aspects.py @@ -49,7 +49,7 @@ from pywikibot.exceptions import ServerError, NoUsername from pywikibot.family import WikimediaFamily from pywikibot.site import BaseSite -from pywikibot.tools import PY2 +from pywikibot.tools import PY2, StringTypes
import tests
@@ -106,6 +106,21 @@ sys.stdout.flush() result.addSuccess(self)
+ def assertMethod(self, method, *args): + """Generic method assertion.""" + if not method(*args): + self.fail('{0!r} ({1!r}) fails'.format(method, args)) + + def assertStringMethod(self, method, *args): + """ + Generic string method assertion. + + All args must be already converted to a string. + """ + for arg in args: + self.assertIsInstance(arg, StringTypes) + self.assertMethod(method, *args) + def assertPageInNamespaces(self, page, namespaces): """ Assert that Pages is in namespaces. diff --git a/tests/site_tests.py b/tests/site_tests.py index 85bdefd..73ff338 100644 --- a/tests/site_tests.py +++ b/tests/site_tests.py @@ -224,6 +224,11 @@ mysite_pickled = pickle.loads(mysite_str) self.assertEqual(mysite, mysite_pickled)
+ def test_repr(self): + """Test __repr__.""" + expect = 'Site("{0}", "{1}")'.format(self.code, self.family) + self.assertStringMethod(str.endswith, repr(self.site), expect) + def testBaseMethods(self): """Test cases for BaseSite methods.""" mysite = self.get_site() @@ -235,9 +240,6 @@ self.assertEqual(mysite.sitename(), "%s:%s" % (self.family, self.code)) - self.assertEqual(repr(mysite), - 'Site("%s", "%s")' - % (self.code, self.family)) self.assertIsInstance(mysite.linktrail(), basestring) self.assertIsInstance(mysite.redirect(), basestring) try: @@ -2757,6 +2759,7 @@ def test_removed_site(self): """Test Wikimedia offline site.""" site = pywikibot.Site('ru-sib', 'wikipedia') + self.assertIsInstance(site, pywikibot.site.RemovedSite) self.assertEqual(site.code, 'ru-sib') self.assertIsInstance(site.obsolete, bool) self.assertTrue(site.obsolete) diff --git a/tests/utils.py b/tests/utils.py index c12f5e2..fb2fafb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -364,12 +364,6 @@ def _build_namespaces(self): return Namespace.builtin_namespaces(case=self.siteinfo['case'])
- def __repr__(self): - """Override default so warnings and errors indicate test is dry.""" - return "%s(%r, %r)" % (self.__class__.__name__, - self.code, - self.family.name) - @property def userinfo(self): """Return dry data."""