John Vandenberg has uploaded a new change for review. https://gerrit.wikimedia.org/r/221637
Change subject: [WIP] Support custom families in pywikibot.families ...................................................................... [WIP] Support custom families in pywikibot.families Bug: T104130 Change-Id: I6a9786b91772f079c8aa3f7dbb34152dbaf41a86 --- R families/pywikibot/families/wikia/lyricwiki_family.py A families/setup_wikia.py M pywikibot/__init__.py M pywikibot/bot.py A pywikibot/config.py M pywikibot/config2.py D pywikibot/families/__init__.py M pywikibot/family.py A pywikibot/tools/py2.py M setup.py 10 files changed, 246 insertions(+), 77 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core refs/changes/37/221637/1 diff --git a/pywikibot/families/lyricwiki_family.py b/families/pywikibot/families/wikia/lyricwiki_family.py similarity index 100% rename from pywikibot/families/lyricwiki_family.py rename to families/pywikibot/families/wikia/lyricwiki_family.py diff --git a/families/setup_wikia.py b/families/setup_wikia.py new file mode 100644 index 0000000..b30b790 --- /dev/null +++ b/families/setup_wikia.py @@ -0,0 +1,25 @@ +from setuptools import setup + +setup( + name='PywikibotWikiaFamily', + version='0.1', + description='Wikia configuration for Pywikibot', + long_description='Wikia configuration for Pywikibot', + maintainer='The Pywikibot team', + maintainer_email='[email protected]', + license='MIT License', + packages=['pywikibot', 'pywikibot.families', 'pywikibot.families.wikia'], + install_requires='pywikibot', + url='https://www.mediawiki.org/wiki/Pywikibot', + classifiers=[ + 'License :: OSI Approved :: MIT License', + 'Development Status :: 4 - Beta', + 'Operating System :: OS Independent', + 'Intended Audience :: Developers', + 'Environment :: Console', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + ], + use_2to3=False, + zip_safe=False +) diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index b1d2cc3..22842d6 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -25,6 +25,10 @@ from warnings import warn +if __path__: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + # Use pywikibot. prefix for all in-package imports; this is to prevent # confusion with similarly-named modules in version 1 framework, for users # who want to continue using both @@ -97,12 +101,12 @@ # ) + textlib_methods # pep257 also doesn't support __all__ multiple times in a document # so instead use this trick -globals()['__all__'] = globals()['__all__'] + textlib_methods +#globals()['__all__'] = globals()['__all__'] + textlib_methods for _name in textlib_methods: target = getattr(textlib, _name) wrapped_func = redirect_func(target) - globals()[_name] = wrapped_func + #globals()[_name] = wrapped_func deprecated = redirect_func(pywikibot.tools.deprecated) @@ -165,8 +169,8 @@ """ return self.strftime(self.ISO8601Format) - toISOformat = redirect_func(isoformat, old_name='toISOformat', - class_name='Timestamp') + #toISOformat = redirect_func(isoformat, old_name='toISOformat', + # class_name='Timestamp') def totimestampformat(self): """Convert object to a MediaWiki internal timestamp.""" @@ -624,10 +628,10 @@ # alias for backwards-compability -getSite = pywikibot.tools.redirect_func(Site, old_name='getSite') +#getSite = pywikibot.tools.redirect_func(Site, old_name='getSite') -from .page import ( +from pywikibot.page import ( Page, FilePage, Category, @@ -637,13 +641,13 @@ PropertyPage, Claim, ) -from .page import html2unicode, url2unicode, unicode2html +from pywikibot.page import html2unicode, url2unicode, unicode2html link_regex = re.compile(r'\[\[(?P<title>[^\]|[<>{}]*)(\|.*?)?\]\]') [email protected]("comment parameter for page saving method") +#@pywikibot.tools.deprecated("comment parameter for page saving method") def setAction(s): """Set a summary to use for changed page submissions.""" config.default_edit_summary = s @@ -748,17 +752,17 @@ _putthread.setName('Put-Thread') _putthread.setDaemon(True) -wrapper = pywikibot.tools.ModuleDeprecationWrapper(__name__) -wrapper._add_deprecated_attr('ImagePage', FilePage) -wrapper._add_deprecated_attr( - 'PageNotFound', pywikibot.exceptions.DeprecatedPageNotFoundError, - warning_message=('{0}.{1} is deprecated, and no longer ' - 'used by pywikibot; use http.fetch() instead.')) -wrapper._add_deprecated_attr( - 'UserActionRefuse', pywikibot.exceptions._EmailUserError, - warning_message='UserActionRefuse is deprecated; ' - 'use UserRightsError and/or NotEmailableError') -wrapper._add_deprecated_attr( - 'QuitKeyboardInterrupt', pywikibot.bot.QuitKeyboardInterrupt, - warning_message='pywikibot.QuitKeyboardInterrupt is deprecated; ' - 'use pywikibot.bot.QuitKeyboardInterrupt instead') +#wrapper = pywikibot.tools.ModuleDeprecationWrapper(__name__) +#wrapper._add_deprecated_attr('ImagePage', FilePage) +#wrapper._add_deprecated_attr( +# 'PageNotFound', pywikibot.exceptions.DeprecatedPageNotFoundError, +# warning_message=('{0}.{1} is deprecated, and no longer ' +# 'used by pywikibot; use http.fetch() instead.')) +#wrapper._add_deprecated_attr( +# 'UserActionRefuse', pywikibot.exceptions._EmailUserError, +# warning_message='UserActionRefuse is deprecated; ' +# 'use UserRightsError and/or NotEmailableError') +#wrapper._add_deprecated_attr( +# 'QuitKeyboardInterrupt', pywikibot.bot.QuitKeyboardInterrupt, +# warning_message='pywikibot.QuitKeyboardInterrupt is deprecated; ' +# 'use pywikibot.bot.QuitKeyboardInterrupt instead') diff --git a/pywikibot/bot.py b/pywikibot/bot.py index 722670e..f09c1b4 100644 --- a/pywikibot/bot.py +++ b/pywikibot/bot.py @@ -40,7 +40,7 @@ import pywikibot from pywikibot import backports -from pywikibot import config +from pywikibot import config2 as config from pywikibot import daemonize from pywikibot import version from pywikibot.bot_choice import ( # noqa: unused imports diff --git a/pywikibot/config.py b/pywikibot/config.py new file mode 120000 index 0000000..442fab7 --- /dev/null +++ b/pywikibot/config.py @@ -0,0 +1 @@ +config2.py \ No newline at end of file diff --git a/pywikibot/config2.py b/pywikibot/config2.py index 0acc717..7ec96e2 100644 --- a/pywikibot/config2.py +++ b/pywikibot/config2.py @@ -320,27 +320,7 @@ break family_files = {} - -def register_family_file(family_name, file_path): - """Register a single family class file.""" - usernames[family_name] = {} - sysopnames[family_name] = {} - disambiguation_comment[family_name] = {} - family_files[family_name] = file_path - - -def register_families_folder(folder_path): - """Register all family class files contained in a directory.""" - for file_name in os.listdir(folder_path): - if file_name.endswith("_family.py"): - family_name = file_name[:-len("_family.py")] - register_family_file(family_name, os.path.join(folder_path, file_name)) - - -# Get the names of all known families, and initialize with empty dictionaries. -# ‘families/’ is a subdirectory of the directory in which config2.py is found. -register_families_folder(os.path.join(os.path.dirname(__file__), 'families')) -register_family_file('wikiapiary', 'https://wikiapiary.com') +family_files['wikiapiary'] = 'https://wikiapiary.com' # Set to True to override the {{bots}} exclusion protocol (at your own risk!) ignore_bot_templates = False diff --git a/pywikibot/families/__init__.py b/pywikibot/families/__init__.py deleted file mode 100644 index ccb2305..0000000 --- a/pywikibot/families/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -"""Families package.""" -# -# (C) Pywikibot team, 2007 -# -# Distributed under the terms of the MIT license. -# -from __future__ import unicode_literals - -__version__ = '$Id$' diff --git a/pywikibot/family.py b/pywikibot/family.py index a4634fc..9a44b4c 100644 --- a/pywikibot/family.py +++ b/pywikibot/family.py @@ -10,12 +10,15 @@ __version__ = '$Id$' # -import sys -import logging -import re import collections import imp +import importlib +import logging +import os +import pkgutil +import re import string +import sys import warnings if sys.version_info[0] > 2: @@ -34,11 +37,102 @@ ) from pywikibot.exceptions import UnknownFamily, FamilyMaintenanceWarning +try: + import pywikibot_families +except ImportError: + pywikibot_families = None + logger = logging.getLogger("pywiki.wiki.family") # Legal characters for Family.name and Family.langs keys NAME_CHARACTERS = string.ascii_letters + string.digits CODE_CHARACTERS = string.ascii_lowercase + string.digits + '-' + +_internal_family_files = None + + +def get_families_files(folder_path=None): + """Register all family class files contained in a directory.""" + if folder_path is None: + global _internal_family_files + if _internal_family_files is not None: + return _internal_family_files + folder_path = os.path.join(os.path.dirname(__file__), 'families') + _internal_family_files = get_families_files(folder_path) + return _internal_family_files + + family_files = {} + if not os.path.exists(folder_path): + return family_files + + for file_name in os.listdir(folder_path): + path = os.path.join(folder_path, file_name) + if file_name.endswith("_family.py"): + family_name = file_name[:-len("_family.py")] + family_files[family_name] = path + elif os.path.isdir(path) and '__' not in file_name: + family_files.update(get_families_files(path)) + + return family_files + + +def package_subpackages(package): + """Get list of subpackages.""" + return [modname for importer, modname, ispkg + in pkgutil.walk_packages(path=package.__path__)] + + +def package_submodules(package): + """Get list of all submodules without loading the leaf modules.""" + modules = [] + prefix = package.__name__ + '.' + for importer, modname, ispkg in pkgutil.walk_packages( + path=package.__path__, + prefix=prefix, + onerror=lambda x: None): + modules.append(modname[len(prefix):]) + return modules + + +def _add_family_names(package, names, ignore_duplicates=False): + """Add names.""" + subpackages = package_subpackages(package) + modules = package_submodules(package) + for mod in modules: + base, dummy, rest = mod.partition('.') + assert base in subpackages + if not dummy: + continue + + assert '.' not in rest + if rest.endswith('_family'): + rest = rest[:-7] + if not ignore_duplicates: + assert rest not in names + + names.add(rest) + + +def family_names(): + """Obtain list of all families.""" + names = set() + + if pywikibot_families: + _add_family_names(pywikibot_families, names) + try: + import pywikibot.families + except ImportError: + pass + else: + _add_family_names(pywikibot.families, names, True) + + # Get the names of all known families by inspecting ‘families/’ which + # is a subdirectory of the directory in which family.py is found. + internal = get_families_files() + + names.update(internal.keys()) + + return names class Family(object): @@ -896,29 +990,47 @@ if fam in Family._families: return Family._families[fam] - if fam in config.family_files: - family_file = config.family_files[fam] - + if fam in pywikibot.config.family_files: + family_file = pywikibot.config.family_files[fam] if family_file.startswith('http://') or family_file.startswith('https://'): myfamily = AutoFamily(fam, family_file) Family._families[fam] = myfamily return Family._families[fam] - elif fam == 'lockwiki': - raise UnknownFamily( - "Family 'lockwiki' has been removed as it not a public wiki.\n" - "You may install your own family file for this wiki, and a " - "old family file may be found at:\n" - "http://git.wikimedia.org/commitdiff/pywikibot%2Fcore.git/dfdc0c9150fa8e09829bb9d236") + raise UnknownFamily( + 'Family %s in pywikibot.config.family_files is not supported. ' + 'Create your own family package following instructions at ' + 'https://www.mediawiki.org/wiki/Manual:Pywikibot/custom_family' + % fam) + + if fam not in family_names(): + raise UnknownFamily( + 'Family name %s is not known. ' + 'Create your own family package following instructions at ' + 'https://www.mediawiki.org/wiki/Manual:Pywikibot/custom_family' + % fam) + + mod = None try: - # Ignore warnings due to dots in family names. - # TODO: use more specific filter, so that family classes can use - # RuntimeWarning's while loading. - with warnings.catch_warnings(): - warnings.simplefilter("ignore", RuntimeWarning) - mod = imp.load_source(fam, config.family_files[fam]) - except (ImportError, KeyError): - raise UnknownFamily(u'Family %s does not exist' % fam) + mod = importlib.import_module('pywikibot.families.%s' % fam) + except ImportError: + try: + mod = importlib.import_module('pywikibot.families.%s_family' % fam) + except ImportError: + pass + + if not mod: + internal = get_families_files() + if fam not in internal: + raise UnknownFamily(u'Family %s does not exist' % fam) + try: + mod = imp.load_source(fam, internal[fam]) + except ImportError as e: + raise UnknownFamily('Family %s (%s) could not be loaded: %s' + % (fam, internal[fam], e)) + + assert mod + cls = mod.Family() if cls.name != fam: warn(u'Family name %s does not match family module name %s' diff --git a/pywikibot/tools/py2.py b/pywikibot/tools/py2.py new file mode 100644 index 0000000..f8fbe74 --- /dev/null +++ b/pywikibot/tools/py2.py @@ -0,0 +1,55 @@ +from __future__ import unicode_literals, absolute_import + +import imp +import os + +from pywikibot.tools import redirect_func + + +class ModuleContextDict(dict): + + """Execution context that updates a module.""" + + def __init__(self, module): + self.__module = module + + def __setitem__(self, item, value): + setattr(self.__module, item, value) + + def __getitem__(self, item): + if item == 'getattr': + return getattr + if item == 'object': + return object + if item == 'False': + return False + if item == 'True': + return True + + return getattr(self.__module, item) + + +def get_real_pywikibot(paths): + """Get the real path for pywikibot.""" + real_pywikibot_dirs = [p for p in paths if 'pywikibot-' in p] + for path in real_pywikibot_dirs: + if os.path.isdir(path): + if os.path.exists(os.path.join(path, 'backports.py')): + return path + + +def fix_base_import(module, paths, name): + """Update pywikibot module with real pywikibot module.""" + pywikibot_dir = get_real_pywikibot(paths) + assert pywikibot_dir + + pywikibot_init = pywikibot_dir + '/__init__.py' + + module.__dict__['redirect_func'] = redirect_func + + context = ModuleContextDict(module) + + with open(pywikibot_init, 'rb') as f: + exec(compile(f.read(), pywikibot_init, 'exec'), context) + + module.__file__ = pywikibot_init diff --git a/setup.py b/setup.py index 972c985..09d3b31 100644 --- a/setup.py +++ b/setup.py @@ -141,6 +141,10 @@ version = '2.0rc1.post2' github_url = 'https://github.com/wikimedia/pywikibot-core' +packages = ['pywikibot', 'pywikibot.families'] +packages.extend(package for package in find_packages() + if package.startswith('pywikibot.')) + setup( name=name, version=version, @@ -149,9 +153,7 @@ maintainer='The Pywikibot team', maintainer_email='[email protected]', license='MIT License', - packages=['pywikibot'] + [package - for package in find_packages() - if package.startswith('pywikibot.')], + packages=packages, install_requires=dependencies, dependency_links=dependency_links, extras_require=extra_deps, -- To view, visit https://gerrit.wikimedia.org/r/221637 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6a9786b91772f079c8aa3f7dbb34152dbaf41a86 Gerrit-PatchSet: 1 Gerrit-Project: pywikibot/core Gerrit-Branch: master Gerrit-Owner: John Vandenberg <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
