jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/563984 )
Change subject: [cleanup] deprecate sysop parameter in site methods and Site function ......................................................................
[cleanup] deprecate sysop parameter in site methods and Site function
config.sysopnames is deprecated since 6f0cafd
- deprecate sysop parameter in several site methods - remove relogin in several methods because sysop flag isn't applicable anymore - remove sysop usage in pywikibot.Site function - update dry_site_tests.py - update utils.py - site._username becomes a string or None - replace all occurences of site._username property with site.username method
Change-Id: Ibd01b2c5f59d5643189c883dff17921bdd436462 --- M pywikibot/__init__.py M pywikibot/site.py M scripts/maintenance/cache.py M tests/api_tests.py M tests/aspects.py M tests/cache_tests.py M tests/dry_api_tests.py M tests/dry_site_tests.py M tests/utils.py 9 files changed, 101 insertions(+), 139 deletions(-)
Approvals: Mpaa: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index 7214aa2..5e33a21 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -1194,6 +1194,7 @@ return _url_cache[url]
+@_deprecate_arg('sysop', None) def Site(code=None, fam=None, user=None, sysop=None, interface=None, url=None): """A factory method to obtain a Site object.
@@ -1208,8 +1209,6 @@ @type fam: str or pywikibot.family.Family @param user: bot user name to use on this site (override config.usernames) @type user: str - @param sysop: sysop user to use on this site (override config.sysopnames) - @type sysop: str @param interface: site class or name of class in pywikibot.site (override config.site_interface) @type interface: subclass of L{pywikibot.site.BaseSite} or string @@ -1247,10 +1246,6 @@ code_to_user.update(config.usernames[family_name]) user = user or code_to_user.get(code) or code_to_user.get('*')
- code_to_sysop = config.sysopnames['*'].copy() - code_to_sysop.update(config.sysopnames[family_name]) - sysop = sysop or code_to_sysop.get(code) or code_to_sysop.get('*') - if not isinstance(interface, type): # If it isn't a class, assume it is a string if PY2: # Must not be unicode in Python 2 @@ -1268,7 +1263,7 @@ user = normalize_username(user) key = '%s:%s:%s:%s' % (interface.__name__, fam, code, user) if key not in _sites or not isinstance(_sites[key], interface): - _sites[key] = interface(code=code, fam=fam, user=user, sysop=sysop) + _sites[key] = interface(code=code, fam=fam, user=user, sysop=None) debug("Instantiated %s object '%s'" % (interface.__name__, _sites[key]), _logger)
diff --git a/pywikibot/site.py b/pywikibot/site.py index a3e72bb..68d291b 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -719,7 +719,8 @@
"""Site methods that are independent of the communication interface."""
- def __init__(self, code, fam=None, user=None, sysop=None): + @remove_last_args(['sysop']) + def __init__(self, code, fam=None, user=None): """ Initializer.
@@ -729,8 +730,6 @@ @type fam: str or pywikibot.family.Family @param user: bot user name (optional) @type user: str - @param sysop: sysop account user name (optional) - @type sysop: str """ if code.lower() != code: # Note the Site function in __init__ also emits a UserWarning @@ -774,7 +773,7 @@ raise UnknownSite("Language '%s' does not exist in family %s" % (self.__code, self.__family.name))
- self._username = [normalize_username(user), normalize_username(sysop)] + self._username = normalize_username(user)
self.use_hard_category_redirects = ( self.code in self.family.use_hard_category_redirects) @@ -884,14 +883,15 @@
def user(self): """Return the currently-logged in bot username, or None.""" - if self.logged_in(True): - return self._username[True] - elif self.logged_in(False): - return self._username[False] + if self.logged_in(): + return self.username() + else: + return None
- def username(self, sysop=False): - """Return the username/sysopname used for the site.""" - return self._username[sysop] + @remove_last_args(['sysop']) + def username(self): + """Return the username used for the site.""" + return self._username
def __getattr__(self, attr): """Delegate undefined methods calls to the Family object.""" @@ -1255,17 +1255,14 @@ @deprecated('pywikibot.data.api.Request or pywikibot.comms.http.request', since='20141225') @deprecated_args(compress=None, no_hostname=None, cookies_only=None, - refer=None, back_response=None) - def getUrl(self, path, retry=None, sysop=None, data=None): + refer=None, back_response=None, retry=None, sysop=None) + def getUrl(self, path, data=None): """DEPRECATED.
Retained for compatibility only. All arguments except path and data are ignored.
""" - if retry is not None or sysop is not None: - warn('APISite.getUrl parameters retry and sysop are not supported', - UserWarning) from pywikibot.comms import http if data: if not isinstance(data, UnicodeType): @@ -1813,9 +1810,10 @@
"""Site removed from a family."""
- def __init__(self, code, fam, user=None, sysop=None): + @remove_last_args(['sysop']) + def __init__(self, code, fam, user=None): """Initializer.""" - super(RemovedSite, self).__init__(code, fam, user, sysop) + super(RemovedSite, self).__init__(code, fam, user)
class APISite(BaseSite): @@ -1826,9 +1824,10 @@ Do not instantiate directly; use pywikibot.Site function. """
- def __init__(self, code, fam=None, user=None, sysop=None): + @remove_last_args(['sysop']) + def __init__(self, code, fam=None, user=None): """Initializer.""" - BaseSite.__init__(self, code, fam, user, sysop) + BaseSite.__init__(self, code, fam, user) self._msgcache = {} self._loginstatus = LoginStatus.NOT_ATTEMPTED self._siteinfo = Siteinfo(self) @@ -1956,34 +1955,29 @@ return self._request_class({'parameters': kwargs}).create_simple( site=self, **kwargs)
- def logged_in(self, sysop=False): + @remove_last_args(['sysop']) + def logged_in(self): """Verify the bot is logged into the site as the expected user.
The expected usernames are those provided as either the user or sysop parameter at instantiation.
- @param sysop: if True, test if user is logged in as the sysop user - instead of the normal user. - @type sysop: bool - @rtype: bool """ if not hasattr(self, '_userinfo'): return False
- if sysop and 'sysop' not in self.userinfo['groups']: - return False - if 'name' not in self.userinfo or not self.userinfo['name']: return False
- if self.userinfo['name'] != self._username[sysop]: + if self.userinfo['name'] != self.username(): return False
return True
@deprecated('Site.user()', since='20090307') - def loggedInAs(self, sysop=False): + @remove_last_args(['sysop']) + def loggedInAs(self): """Return the current username if logged in, otherwise return None.
DEPRECATED (use .user() method instead) @@ -1994,7 +1988,7 @@
@rtype: bool """ - return self.logged_in(sysop) and self.user() + return self.logged_in() and self.user()
def is_oauth_token_available(self): """ @@ -2005,13 +1999,11 @@ auth_token = get_authentication(self.base_url('')) return auth_token is not None and len(auth_token) == 4
- def login(self, sysop=False, autocreate=False): + @deprecated_args(sysop=None) + def login(self, sysop=None, autocreate=False): """ Log the user in if not already logged in.
- @param sysop: if true, log in with the sysop account. - @type sysop: bool - @param autocreate: if true, allow auto-creation of the account using unified login @type autocreate: bool @@ -2029,24 +2021,21 @@ # of issues are resolved. if self._loginstatus == LoginStatus.IN_PROGRESS: pywikibot.log( - '%r.login(%r) called when a previous login was in progress.' - % (self, sysop) - ) + '{!r}.login() called when a previous login was in progress.' + .format(self)) # There are several ways that the site may already be # logged in, and we do not need to hit the server again. # logged_in() is False if _userinfo exists, which means this # will have no effect for the invocation from api.py - if self.logged_in(sysop): - self._loginstatus = (LoginStatus.AS_SYSOP - if sysop else LoginStatus.AS_USER) + if self.logged_in(): + self._loginstatus = LoginStatus.AS_USER return # check whether a login cookie already exists for this user # or check user identity when OAuth enabled self._loginstatus = LoginStatus.IN_PROGRESS try: self.getuserinfo(force=True) - if self.userinfo['name'] == self._username[sysop] and \ - self.logged_in(sysop): + if self.userinfo['name'] == self.user(): return # May occur if you are not logged in (no API read permissions). except api.APIError: @@ -2056,10 +2045,8 @@ raise e
if self.is_oauth_token_available(): - if sysop: - raise NoUsername('No sysop is permitted with OAuth') - elif self.userinfo['name'] != self._username[sysop]: - if self._username == [None, None]: + if self.userinfo['name'] != self.username(): + if self.username() is None: raise NoUsername('No username has been defined in your ' 'user-config.py: you have to add in this ' 'file the following line:\n' @@ -2073,15 +2060,14 @@ '{wrong}, but expect as {right}' .format(site=self, wrong=self.userinfo['name'], - right=self._username[sysop])) + right=self.username())) else: raise NoUsername('Logging in on %s via OAuth failed' % self) - login_manager = api.LoginManager(site=self, user=self._username[sysop]) + login_manager = api.LoginManager(site=self, user=self.username()) if login_manager.login(retry=True, autocreate=autocreate): - self._username[sysop] = login_manager.username + self._username = login_manager.username self.getuserinfo(force=True) - self._loginstatus = (LoginStatus.AS_SYSOP - if sysop else LoginStatus.AS_USER) + self._loginstatus = LoginStatus.AS_USER else: self._loginstatus = LoginStatus.NOT_LOGGED_IN # failure
@@ -2186,7 +2172,8 @@
globaluserinfo = property(fget=getglobaluserinfo, doc=getuserinfo.__doc__)
- def is_blocked(self, sysop=False): + @remove_last_args(['sysop']) + def is_blocked(self): """ Return True when logged in user is blocked.
@@ -2194,26 +2181,20 @@ the method has_right should be used. U{https://www.mediawiki.org/wiki/API:Userinfo%7D
- @param sysop: If true, log in to sysop account (if available) - @type sysop: bool @rtype: bool """ - if not self.logged_in(sysop): - self.login(sysop) return 'blockinfo' in self._userinfo
@deprecated('has_right() or is_blocked()', since='20141218') - def checkBlocks(self, sysop=False): + @remove_last_args(['sysop']) + def checkBlocks(self): """ Raise an exception when the user is blocked. DEPRECATED.
- @param sysop: If true, log in to sysop account (if available) - @type sysop: bool - @raises pywikibot.exceptions.UserBlocked: The logged in user/sysop - account is blocked. + @raises pywikibot.exceptions.UserBlocked: The logged in user account + is blocked. """ - if self.is_blocked(sysop): - # User blocked + if self.is_blocked(): raise UserBlocked('User is blocked in site %s' % self)
def get_searched_namespaces(self, force=False): @@ -2286,7 +2267,8 @@ start=start, end=end, reverse=reverse, is_ts=is_ts))
- def has_right(self, right, sysop=False): + @remove_last_args(['sysop']) + def has_right(self, right): """Return true if and only if the user has a specific right.
Possible values of 'right' may vary depending on wiki settings, @@ -2297,26 +2279,21 @@
U{https://www.mediawiki.org/wiki/API:Userinfo%7D """ - if not self.logged_in(sysop): - self.login(sysop) return right.lower() in self._userinfo['rights']
- def has_group(self, group, sysop=False): + @remove_last_args(['sysop']) + def has_group(self, group): """Return true if and only if the user is a member of specified group.
Possible values of 'group' may vary depending on wiki settings, but will usually include bot. U{https://www.mediawiki.org/wiki/API:Userinfo%7D - """ - if not self.logged_in(sysop): - self.login(sysop) return group.lower() in self._userinfo['groups']
- def messages(self, sysop=False): + @remove_last_args(['sysop']) + def messages(self): """Return true if the user has new messages, and false otherwise.""" - if not self.logged_in(sysop): - self.login(sysop) return 'messages' in self._userinfo
@need_extension('Echo') @@ -3591,12 +3568,13 @@ return self.tokens[tokentype]
@deprecated("the 'tokens' property", since='20150218') - def getToken(self, getalways=True, getagain=False, sysop=False): + @remove_last_args(['sysop']) + def getToken(self, getalways=True, getagain=False): """DEPRECATED: Get edit token.""" - if self.username(sysop) != self.user(): + if self.username() != self.user(): raise ValueError('The token for {0} was requested but only the ' 'token for {1} can be retrieved.'.format( - self.username(sysop), self.user())) + self.username(), self.user())) if not getalways: raise ValueError('In pywikibot/core getToken does not support the ' 'getalways parameter.') @@ -3607,12 +3585,13 @@ return self.tokens[token]
@deprecated("the 'tokens' property", since='20150218') - def getPatrolToken(self, sysop=False): + @remove_last_args(['sysop']) + def getPatrolToken(self): """DEPRECATED: Get patrol token.""" - if self.username(sysop) != self.user(): + if self.username() != self.user(): raise ValueError('The token for {0} was requested but only the ' 'token for {1} can be retrieved.'.format( - self.username(sysop), self.user())) + self.username(), self.user())) return self.tokens['patrol']
# following group of methods map more-or-less directly to API queries @@ -4016,12 +3995,11 @@ return self._generator(api.PageGenerator, namespaces=namespaces, total=total, g_content=content, **cmargs)
- @deprecated_args(getText='content') + @deprecated_args(getText='content', sysop=None) def loadrevisions(self, page, content=False, revids=None, startid=None, endid=None, starttime=None, endtime=None, rvdir=None, user=None, excludeuser=None, - section=None, sysop=False, step=None, total=None, - rollback=False): + section=None, step=None, total=None, rollback=False): """Retrieve revision information and store it in page object.
By default, retrieves the last (current) revision of the page, @@ -4056,8 +4034,6 @@ if true, retrieve earliest first @param user: retrieve only revisions authored by this user @param excludeuser: retrieve all revisions not authored by this user - @param sysop: if True, switch to sysop account (if available) to - retrieve this page @raises ValueError: invalid startid/endid or starttime/endtime values @raises pywikibot.Error: revids belonging to a different page """ @@ -4104,7 +4080,6 @@ if section is not None: rvargs['rvsection'] = UnicodeType(section) if rollback: - self.login(sysop=sysop) rvargs['rvtoken'] = 'rollback' if revids is None: rvtitle = page.title(with_section=False).encode(self.encoding()) @@ -4132,7 +4107,6 @@ rvargs['rvuser'] = user elif excludeuser: rvargs['rvexcludeuser'] = excludeuser - # TODO if sysop: something
# assemble API request rvgen = self._generator(api.PropertyGenerator, total=total, **rvargs) @@ -7539,15 +7513,13 @@ """ return self.moderate_post(post, 'restore', reason)
- @deprecated_args(step=None) - def watched_pages(self, sysop=False, force=False, total=None): + @deprecated_args(step=None, sysop=None) + def watched_pages(self, force=False, total=None): """ Return watchlist.
@see: U{https://www.mediawiki.org/wiki/API:Watchlistraw%7D
- @param sysop: Returns watchlist of sysop user if true - @type sysop: bool @param force_reload: Reload watchlist @type force_reload: bool @param total: if not None, limit the generator to yielding this many @@ -7556,7 +7528,6 @@ @return: list of pages in watchlist @rtype: list of pywikibot.Page objects """ - self.login(sysop=sysop) expiry = None if force else pywikibot.config.API_config_expiry gen = api.PageGenerator(site=self, generator='watchlistraw', expiry=expiry) @@ -7590,9 +7561,10 @@ class ClosedSite(APISite): """Site closed to read-only mode."""
- def __init__(self, code, fam, user=None, sysop=None): + @remove_last_args(['sysop']) + def __init__(self, code, fam, user=None): """Initializer.""" - super(ClosedSite, self).__init__(code, fam, user, sysop) + super(ClosedSite, self).__init__(code, fam, user)
def _closed_error(self, notice=''): """An error instead of pointless API call.""" diff --git a/scripts/maintenance/cache.py b/scripts/maintenance/cache.py index 7e5ea68..e771756 100755 --- a/scripts/maintenance/cache.py +++ b/scripts/maintenance/cache.py @@ -65,7 +65,7 @@ uniquedesc(entry) """ # -# (C) Pywikibot team, 2014-2019 +# (C) Pywikibot team, 2014-2020 # # Distributed under the terms of the MIT license. # @@ -206,7 +206,7 @@ if login_status: self.site._loginstatus = eval(login_status) if username: - self.site._username = [username, username] + self.site._username = username if not params: raise ParseError('No request params') self._params = {} diff --git a/tests/api_tests.py b/tests/api_tests.py index c25e790..07e6360 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -1018,7 +1018,7 @@ self, error, warning, exception, output ): """Test the query with a username which does not exist.""" - self.site._username = ['Not registered username', None] + self.site._username = 'Not registered username' req = api.Request(site=self.site, parameters={'action': 'query'}) self.assertRaises(pywikibot.NoUsername, req.submit) # FIXME: T100965 @@ -1047,7 +1047,7 @@ @patch.object(pywikibot.config, 'usernames', defaultdict(dict)) def test_access_denied_no_username(self, exception, warning): """Test the query without a username.""" - self.site._username = [None, None] + self.site._username = None req = api.Request(site=self.site, parameters={'action': 'query'}) self.assertRaises(pywikibot.NoUsername, req.submit) # FIXME: T100965 diff --git a/tests/aspects.py b/tests/aspects.py index a9ac065..0aaffab 100644 --- a/tests/aspects.py +++ b/tests/aspects.py @@ -428,7 +428,7 @@
"""Site interface to prevent sites being loaded."""
- def __init__(self, code, fam=None, user=None, sysop=None): + def __init__(self, code, fam=None, user=None): """Initializer.""" raise pywikibot.SiteDefinitionError( 'Loading site {}:{} during dry test not permitted' @@ -639,7 +639,7 @@ @classmethod def require_site_user(cls, family, code, sysop=False): """Check the user config has a valid login to the site.""" - if not cls.has_site_user(family, code, sysop=sysop): + if not cls.has_site_user(family, code): raise unittest.SkipTest( '{}: No {}username for {}:{}' .format(cls.__name__, @@ -673,14 +673,14 @@ site, site.siteinfo.get('readonlyreason', '')))
try: - site.login(sysop) + site.login() except NoUsername: pass
if not site.user(): raise unittest.SkipTest( - '{}: Not able to login to {} as {}' - .format(cls.__name__, 'sysop' if sysop else 'bot', site)) + '{}: Not able to login to {}' + .format(cls.__name__, site))
def setUp(self): """ @@ -698,8 +698,6 @@
def _reset_login(self): """Login to all sites.""" - sysop = hasattr(self, 'sysop') and self.sysop - # There may be many sites, and setUp doesn't know # which site is to be tested; ensure they are all # logged in. @@ -709,8 +707,8 @@ if hasattr(self, 'oauth') and self.oauth: continue
- if not site.logged_in(sysop): - site.login(sysop) + if not site.logged_in(): + site.login() assert(site.user())
def get_userpage(self, site=None): @@ -1036,7 +1034,7 @@ return cls.sites[name]['site']
@classmethod - def has_site_user(cls, family, code, sysop=False): + def has_site_user(cls, family, code): """Check the user config has a user for the site.""" if not family: raise Exception('no family defined for {}'.format(cls.__name__)) @@ -1044,7 +1042,7 @@ raise Exception('no site code defined for {}' .format(cls.__name__))
- usernames = config.sysopnames if sysop else config.usernames + usernames = config.usernames
return (code in usernames[family] or '*' in usernames[family] or code in usernames['*'] or '*' in usernames['*']) diff --git a/tests/cache_tests.py b/tests/cache_tests.py index 526347c..b2c8cae 100644 --- a/tests/cache_tests.py +++ b/tests/cache_tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """API Request cache tests.""" # -# (C) Pywikibot team, 2012-2018 +# (C) Pywikibot team, 2012-2020 # # Distributed under the terms of the MIT license. # @@ -25,9 +25,9 @@ """Assert validity of the cache entry.""" self.assertIsInstance(entry.site, BaseSite) self.assertIsInstance(entry.site._loginstatus, int) - self.assertIsInstance(entry.site._username, list) + self.assertNotIsInstance(entry.site._username, list) if entry.site._loginstatus >= 1: - self.assertIsNotNone(entry.site._username[0]) + self.assertIsNotNone(entry.site._username) self.assertIsInstance(entry._params, dict) self.assertIsNotNone(entry._params) # TODO: more tests on entry._params, and possibly fixes needed diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py index 268d6cf..348a887 100644 --- a/tests/dry_api_tests.py +++ b/tests/dry_api_tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """API tests which do not interact with a site.""" # -# (C) Pywikibot team, 2012-2019 +# (C) Pywikibot team, 2012-2020 # # Distributed under the terms of the MIT license. # @@ -260,7 +260,7 @@ """Test Request object when username is not correct.""" site = self.get_site() site._userinfo = {'name': 'other_username', 'groups': []} - site._username[0] = 'myusername' + site._username = 'myusername' # Ignore warning: API write action by unexpected username commenced. with patch('pywikibot.warning'): Request(site=site, parameters={'action': 'edit'}) @@ -269,7 +269,7 @@ """Test Request object when username is correct.""" site = self.get_site() site._userinfo = {'name': 'myusername', 'groups': []} - site._username[0] = 'myusername' + site._username = 'myusername'
Request(site=site, parameters={'action': 'edit'})
@@ -310,7 +310,7 @@ """Test Request object prepared to upload.""" # fake write test needs the config username site = self.get_site() - site._username[0] = 'myusername' + site._username = 'myusername' site._userinfo = {'name': 'myusername', 'groups': []} parameters = {'action': 'upload', 'file': 'MP_sounds.png', 'filename': join_images_path('MP_sounds.png')} diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py index 95d0ffc..a37454f 100644 --- a/tests/dry_site_tests.py +++ b/tests/dry_site_tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Tests against a fake Site object.""" # -# (C) Pywikibot team, 2012-2019 +# (C) Pywikibot team, 2012-2020 # # Distributed under the terms of the MIT license. # @@ -32,26 +32,23 @@ x = self.get_site()
x._userinfo = {'name': None, 'groups': []} - x._username = ['normal_user', 'sysop_user'] + x._username = 'user'
- self.assertFalse(x.logged_in(True)) - self.assertFalse(x.logged_in(False)) + self.assertFalse(x.logged_in())
- x._userinfo['name'] = 'normal_user' - self.assertFalse(x.logged_in(True)) - self.assertTrue(x.logged_in(False)) + x._userinfo['name'] = 'user' + self.assertTrue(x.logged_in())
- x._userinfo['name'] = 'sysop_user' + x._userinfo['name'] = 'user' x._userinfo['groups'] = ['sysop'] - self.assertTrue(x.logged_in(True)) - self.assertFalse(x.logged_in(False)) + self.assertTrue(x.logged_in())
def test_user_agent(self): """Test different variants of user agents.""" x = self.get_site()
x._userinfo = {'name': 'foo'} - x._username = ('foo', None) + x._username = 'foo'
self.assertEqual('Pywikibot/' + pywikibot.__version__, user_agent(x, format_string='{pwb}')) @@ -67,12 +64,12 @@ user_agent(x, format_string='{username}'))
x._userinfo = {'name': '!'} - x._username = ('!', None) + x._username = '!'
self.assertEqual('!', user_agent(x, format_string='{username}'))
x._userinfo = {'name': 'foo bar'} - x._username = ('foo bar', None) + x._username = 'foo bar'
self.assertEqual('foo_bar', user_agent(x, format_string='{username}'))
@@ -86,13 +83,13 @@ user_agent(x, format_string=old_config))
x._userinfo = {'name': '⁂'} - x._username = ('⁂', None) + x._username = '⁂'
self.assertEqual('%E2%81%82', user_agent(x, format_string='{username}'))
x._userinfo = {'name': '127.0.0.1'} - x._username = (None, None) + x._username = None
self.assertEqual('Foo', user_agent(x, format_string='Foo {username}')) self.assertEqual('Foo (' + x.family.name + ':' + x.code + ')', diff --git a/tests/utils.py b/tests/utils.py index 1d917cb..0154dcb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -421,7 +421,7 @@
def __init__(self, code, fam, user, sysop): """Initializer.""" - super(DrySite, self).__init__(code, fam, user, sysop) + super(DrySite, self).__init__(code, fam, user) self._userinfo = pywikibot.tools.EMPTY_DEFAULT self._paraminfo = DryParamInfo() self._siteinfo = DummySiteinfo({})