jenkins-bot has submitted this change and it was merged.
Change subject: Make obsolete site object can be created ......................................................................
Make obsolete site object can be created
Per discussion, this patch makes it possible to create an obsolete site object while the site object will be prevented from writing. In other words, it will be a read-only site. Consequently, this patch solves bug 55146. Note that although obsolete sites now become readable, they will not be in site.languages().
In addition, this patch makes pagelanglinks() return only non-obsolete sites by default to make old scripts still functional, but it adds a parameter to suppress this functionality.
Tests adapted by Merlijn van Deen valhallasw@gmail.com
Bug: 61120 Bug: 55146
Change-Id: I42a2f45bb0d63e268c88a6b0c67a096d2433f21a Original-Change-Id: Ia12660ae2c16148ee7e13977155f1940fe560df1 Original-Change-Id: I3d6ef66c369da7f0b64b821d7d78454192601839 --- M pywikibot/page.py M pywikibot/site.py M tests/dry_site_tests.py M tests/site_tests.py 4 files changed, 133 insertions(+), 135 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py index f95b39b..7a20302 100644 --- a/pywikibot/page.py +++ b/pywikibot/page.py @@ -997,31 +997,43 @@ # ignore any links with invalid contents continue
- def langlinks(self): + def langlinks(self, include_obsolete=False): """Return a list of all interlanguage Links on this page.
+ @param include_obsolete: if true, return even Link objects whose site + is obsolete + """ - # Data might have been preloaded + # Note: We preload a list of *all* langlinks, including links to + # obsolete sites, and store that in self._langlinks. We then filter + # this list if the method was called with include_obsolete=False + # (which is the default) if not hasattr(self, '_langlinks'): - self._langlinks = list(self.iterlanglinks()) + self._langlinks = list(self.iterlanglinks(include_obsolete=True))
- return self._langlinks + if include_obsolete: + return self._langlinks + else: + return filter(lambda i: not i.site.obsolete, self._langlinks)
- def iterlanglinks(self, step=None, total=None): + def iterlanglinks(self, step=None, total=None, include_obsolete=False): """Iterate all interlanguage links on this page.
@param step: limit each API call to this number of pages @param total: iterate no more than this number of pages in total + @param include_obsolete: if true, yield even Link object whose site + is obsolete @return: a generator that yields Link objects.
""" if hasattr(self, '_langlinks'): - return iter(self._langlinks) + return iter(self.langlinks(include_obsolete=include_obsolete)) # XXX We might want to fill _langlinks when the Site # method is called. If we do this, we'll have to think # about what will happen if the generator is not completely # iterated upon. - return self.site.pagelanglinks(self, step=step, total=total) + return self.site.pagelanglinks(self, step=step, total=total, + include_obsolete=include_obsolete)
def data_item(self): """ diff --git a/pywikibot/site.py b/pywikibot/site.py index 0d1e92d..14b916a 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -129,15 +129,15 @@ else: self.__family = fam
+ self.obsolete = False # if we got an outdated language code, use the new one instead. if self.__code in self.__family.obsolete: if self.__family.obsolete[self.__code] is not None: self.__code = self.__family.obsolete[self.__code] else: # no such language anymore - raise NoSuchSite("Language %s in family %s is obsolete" - % (self.__code, self.__family.name)) - if self.__code not in self.languages(): + self.obsolete = True + elif self.__code not in self.languages(): if self.__family.name in list(self.__family.langs.keys()) and \ len(self.__family.langs) == 1: oldcode = self.__code @@ -707,10 +707,15 @@ @param right: the rights the logged in user should have not supported yet and thus ignored. @returns: a decorator to make sure the requirement is statisfied when - the decorated function is called. + the decorated function is called. The function can be called + with as_group='sysop' to override the group set in the + decorator. """ def decorator(fn): def callee(self, *args, **kwargs): + if self.obsolete: + raise NoSuchSite("Language %s in family %s is obsolete" + % (self.code, self.family.name)) grp = kwargs.pop('as_group', group) if grp == 'user': self.login(False) @@ -1959,8 +1964,14 @@ # No such function in the API (this method isn't called anywhere) raise NotImplementedError
- def pagelanglinks(self, page, step=None, total=None): - """Iterate all interlanguage links on page, yielding Link objects.""" + def pagelanglinks(self, page, step=None, total=None, + include_obsolete=False): + """Iterate all interlanguage links on page, yielding Link objects. + + @param include_obsolete: if true, yield even Link objects whose + site is obsolete + + """ lltitle = page.title(withSection=False) llquery = self._generator(api.PropertyGenerator, type_arg="langlinks", @@ -1974,9 +1985,13 @@ if 'langlinks' not in pageitem: continue for linkdata in pageitem['langlinks']: - yield pywikibot.Link.langlinkUnsafe(linkdata['lang'], - linkdata['*'], - source=self) + link = pywikibot.Link.langlinkUnsafe(linkdata['lang'], + linkdata['*'], + source=self) + if link.site.obsolete and not include_obsolete: + continue + else: + yield link
def page_extlinks(self, page, step=None, total=None): """Iterate all external links on page, yielding URL strings.""" diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py index 61b6452..8aef5e1 100644 --- a/tests/dry_site_tests.py +++ b/tests/dry_site_tests.py @@ -1,73 +1,95 @@ import pywikibot +from pywikibot.site import must_be
-from utils import unittest +from utils import PywikibotTestCase, unittest
class DrySite(pywikibot.site.APISite): - @property - def userinfo(self): - return self._userinfo - - -class TestDrySite(unittest.TestCase): - def test_logged_in(self): - x = DrySite('en') - - x._userinfo = {'name': None, 'groups': []} - x._username = ['normal_user', 'sysop_user'] - - self.assertFalse(x.logged_in(True)) - self.assertFalse(x.logged_in(False)) - - x._userinfo['name'] = 'normal_user' - self.assertFalse(x.logged_in(True)) - self.assertTrue(x.logged_in(False)) - - x._userinfo['name'] = 'sysop_user' - x._userinfo['groups'] = ['sysop'] - self.assertTrue(x.logged_in(True)) - self.assertFalse(x.logged_in(False)) - - -class SiteMock(object): - last_login = None - last_fn_called = False - - def login(self, as_sysop): - self.last_login = 'sysop' if as_sysop else 'user' - return True - - def inner_fn(self, *args, **kwargs): - self.last_fn_called = (args, kwargs) - return args, kwargs - - -class TestSiteMock(unittest.TestCase): - def test_must_be_user(self): - x = SiteMock() - wrapped_inner = pywikibot.site.must_be(group='user')(x.inner_fn) - self.assertEqual( - wrapped_inner(x, 1, 2, 3, a='a', b='b'), - ( - (x, 1, 2, 3), - {'a': 'a', 'b': 'b'} - ) - ) - self.assertEqual(x.last_fn_called, ((x, 1, 2, 3), {'a': 'a', 'b': 'b'})) - self.assertEqual(x.last_login, 'user') - - def test_must_be_sysop(self): - x = SiteMock() - wrapped_inner = pywikibot.site.must_be(group='sysop')(x.inner_fn) - self.assertEqual( - wrapped_inner(x, 1, 2, 3, a='a', b='b'), - ( - (x, 1, 2, 3), - {'a': 'a', 'b': 'b'} - ) - ) - self.assertEqual(x.last_fn_called, ((x, 1, 2, 3), {'a': 'a', 'b': 'b'})) - self.assertEqual(x.last_login, 'sysop') - -if __name__ == '__main__': - unittest.main() + @property + def userinfo(self): + return self._userinfo + + +class TestDrySite(PywikibotTestCase): + def test_logged_in(self): + x = DrySite('en') + + x._userinfo = {'name': None, 'groups': []} + x._username = ['normal_user', 'sysop_user'] + + self.assertFalse(x.logged_in(True)) + self.assertFalse(x.logged_in(False)) + + x._userinfo['name'] = 'normal_user' + self.assertFalse(x.logged_in(True)) + self.assertTrue(x.logged_in(False)) + + x._userinfo['name'] = 'sysop_user' + x._userinfo['groups'] = ['sysop'] + self.assertTrue(x.logged_in(True)) + self.assertFalse(x.logged_in(False)) + + +class TestMustBe(PywikibotTestCase): + """Test cases for the must_be decorator.""" + + # Implemented without setUpClass(cls) and global variables as objects + # were not completely disposed and recreated but retained 'memory' + def setUp(self): + self.code = 'test' + self.family = lambda: None + self.family.name = 'test' + self._logged_in_as = None + self.obsolete = False + + def login(self, sysop): + # mock call + self._logged_in_as = 'sysop' if sysop else 'user' + + def testMockInTest(self): + self.assertEqual(self._logged_in_as, None) + self.login(True) + self.assertEqual(self._logged_in_as, 'sysop') + + testMockInTestReset = testMockInTest + + @must_be('sysop') + def call_this_sysop_req_function(self, *args, **kwargs): + return args, kwargs + + @must_be('user') + def call_this_user_req_function(self, *args, **kwargs): + return args, kwargs + + def testMustBeSysop(self): + args = (1, 2, 'a', 'b') + kwargs = {'i': 'j', 'k': 'l'} + retval = self.call_this_sysop_req_function(*args, **kwargs) + self.assertEqual(retval[0], args) + self.assertEqual(retval[1], kwargs) + self.assertEqual(self._logged_in_as, 'sysop') + + def testMustBeUser(self): + args = (1, 2, 'a', 'b') + kwargs = {'i': 'j', 'k': 'l'} + retval = self.call_this_user_req_function(*args, **kwargs) + self.assertEqual(retval[0], args) + self.assertEqual(retval[1], kwargs) + self.assertEqual(self._logged_in_as, 'user') + + def testOverrideUserType(self): + args = (1, 2, 'a', 'b') + kwargs = {'i': 'j', 'k': 'l'} + retval = self.call_this_user_req_function(*args, as_group='sysop', **kwargs) + self.assertEqual(retval[0], args) + self.assertEqual(retval[1], kwargs) + self.assertEqual(self._logged_in_as, 'sysop') + + def testObsoleteSite(self): + self.obsolete = True + args = (1, 2, 'a', 'b') + kwargs = {'i': 'j', 'k': 'l'} + self.assertRaises(pywikibot.NoSuchSite, self.call_this_user_req_function, args, kwargs) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/site_tests.py b/tests/site_tests.py index 79db3e2..efeb3e7 100644 --- a/tests/site_tests.py +++ b/tests/site_tests.py @@ -1107,57 +1107,6 @@ # TODO test other optional arguments
-class TestMustBe(PywikibotTestCase): - """Test cases for the must_be decorator.""" - - # Implemented without setUpClass(cls) and global variables as objects - # were not completely disposed and recreated but retained 'memory' - def setUp(self): - self._logged_in_as = None - - def login(self, sysop): - # mock call - self._logged_in_as = 'sysop' if sysop else 'user' - - def testMockInTest(self): - self.assertEqual(self._logged_in_as, None) - self.login(True) - self.assertEqual(self._logged_in_as, 'sysop') - - testMockInTestReset = testMockInTest - - @must_be('sysop') - def call_this_sysop_req_function(self, *args, **kwargs): - return args, kwargs - - @must_be('user') - def call_this_user_req_function(self, *args, **kwargs): - return args, kwargs - - def testMustBeSysop(self): - args = (1, 2, 'a', 'b') - kwargs = {'i': 'j', 'k': 'l'} - retval = self.call_this_sysop_req_function(*args, **kwargs) - self.assertEqual(retval[0], args) - self.assertEqual(retval[1], kwargs) - self.assertEqual(self._logged_in_as, 'sysop') - - def testMustBeUser(self): - args = (1, 2, 'a', 'b') - kwargs = {'i': 'j', 'k': 'l'} - retval = self.call_this_user_req_function(*args, **kwargs) - self.assertEqual(retval[0], args) - self.assertEqual(retval[1], kwargs) - self.assertEqual(self._logged_in_as, 'user') - - def testOverrideUserType(self): - args = (1, 2, 'a', 'b') - kwargs = {'i': 'j', 'k': 'l'} - retval = self.call_this_user_req_function(*args, as_group='sysop', **kwargs) - self.assertEqual(retval[0], args) - self.assertEqual(retval[1], kwargs) - self.assertEqual(self._logged_in_as, 'sysop') - if __name__ == '__main__': try: try:
pywikibot-commits@lists.wikimedia.org