diff --git a/.moban.d/docs/source/index.rst.jj2 b/.moban.d/docs/source/custom_index.rst.jj2 similarity index 100% rename from .moban.d/docs/source/index.rst.jj2 rename to .moban.d/docs/source/custom_index.rst.jj2 diff --git a/.moban.yml b/.moban.yml index 216b88e..5735ebb 100644 --- a/.moban.yml +++ b/.moban.yml @@ -9,7 +9,7 @@ targets: - MANIFEST.in: MANIFEST.in.jj2 - requirements.txt: requirements.txt.jj2 - "docs/source/conf.py": "docs/source/myconf.py.jj2" - - "docs/source/index.rst": "docs/source/index.rst.jj2" + - "docs/source/index.rst": "docs/source/custom_index.rst.jj2" - test.sh: test.sh.jj2 - "lml/_version.py": _version.py.jj2 - output: CHANGELOG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4bdc70f..f15661f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,15 @@ Change log ================================================================================ +0.0.4 - 07.08.2018 +-------------------------------------------------------------------------------- + +Added +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +#. `#4 `_: to find plugin names with + different naming patterns + 0.0.3 - 12/06/2018 -------------------------------------------------------------------------------- diff --git a/changelog.yml b/changelog.yml index 0d1b8ec..4c3dd17 100644 --- a/changelog.yml +++ b/changelog.yml @@ -1,6 +1,12 @@ name: lml organisation: chfw releases: +- changes: + - action: Added + details: + - "`#4`: to find plugin names with different naming patterns" + date: 07.08.2018 + version: 0.0.4 - changes: - action: Added details: diff --git a/docs/source/conf.py b/docs/source/conf.py index e3a47a4..87b3e31 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,39 +3,174 @@ DESCRIPTION = ( 'Load me later. A lazy plugin management system.' + '' ) +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- Project information ----------------------------------------------------- + +project = u'lml' +copyright = u'2017-2018 Onni Software Ltd.' +author = u'C.W.' + +# The short X.Y version +version = u'0.0.4' +# The full version, including alpha/beta/rc tags +release = u'0.0.4' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinxcontrib.spelling' ] +# Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] source_suffix = '.rst' + +# The master toctree document. master_doc = 'index' -project = u'lml' -copyright = u'2017-2018 Onni Software Ltd.' -version = '0.0.3' -release = '0.0.3' +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -html_theme = 'default' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. htmlhelp_basename = 'lmldoc' -latex_elements = {} + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'lml.tex', - 'lml Documentation', - 'Onni Software Ltd.', 'manual'), + (master_doc, 'lml.tex', u'lml Documentation', + u'Onni Software Ltd.', 'manual'), ] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'lml', - 'lml Documentation', - [u'Onni Software Ltd.'], 1) + (master_doc, 'lml', u'lml Documentation', + [author], 1) ] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'lml', u'lml Documentation', + author, 'lml', 'One line description of project.', + 'Miscellaneous'), +] + +# -- Extension configuration ------------------------------------------------- +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} +# TODO: html_theme not configurable upstream +html_theme = 'default' + +# TODO: DESCRIPTION not configurable upstream texinfo_documents = [ ('index', 'lml', 'lml Documentation', @@ -43,3 +178,5 @@ texinfo_documents = [ DESCRIPTION, 'Miscellaneous'), ] +intersphinx_mapping.update({ +}) diff --git a/lml.yml b/lml.yml index f031f8c..cc0368f 100644 --- a/lml.yml +++ b/lml.yml @@ -4,10 +4,11 @@ organisation: "chfw" author: "C.W." contact: "wangc_2011@hotmail.com" company: "Onni Software Ltd." -version: "0.0.3" -current_version: "0.0.3" -release: "0.0.3" +version: "0.0.4" +current_version: "0.0.4" +release: "0.0.4" copyright_year: 2017-2018 license: New BSD dependencies: [] description: "Load me later. A lazy plugin management system." + diff --git a/lml/__init__.py b/lml/__init__.py index 602898d..65b2db3 100644 --- a/lml/__init__.py +++ b/lml/__init__.py @@ -14,11 +14,13 @@ from lml._version import __author__ # flake8: noqa try: from logging import NullHandler except ImportError: + class NullHandler(logging.Handler): """ - Null handler for logging - """ + Null handler for logging + """ + def emit(self, record): pass -logging.getLogger(__name__).addHandler(NullHandler()) + logging.getLogger(__name__).addHandler(NullHandler()) diff --git a/lml/_version.py b/lml/_version.py index f4af5d3..b5e9002 100644 --- a/lml/_version.py +++ b/lml/_version.py @@ -1,2 +1,2 @@ -__version__ = '0.0.3' -__author__ = 'C.W.' +__version__ = "0.0.4" +__author__ = "C.W." diff --git a/lml/loader.py b/lml/loader.py index f5840f4..0a633ec 100644 --- a/lml/loader.py +++ b/lml/loader.py @@ -9,6 +9,7 @@ :copyright: (c) 2017-2018 by Onni Software Ltd. :license: New BSD License, see LICENSE for more details """ +import re import pkgutil import logging from itertools import chain @@ -18,7 +19,13 @@ from lml.utils import do_import log = logging.getLogger(__name__) -def scan_plugins(prefix, path, black_list=None, white_list=None): +def scan_plugins( + prefix, + pyinstaller_path, + black_list=None, + white_list=None, + plugin_name_patterns=None, +): """ Implicitly discover plugins via pkgutil and pyinstaller path @@ -36,7 +43,49 @@ def scan_plugins(prefix, path, black_list=None, white_list=None): will be auto-loaded: robotchef_britishcuisine, robotchef_chinesecuisine, etc. - path:string + pyinstaller_path:string + used in pyinstaller only. When your end developer would package + your main library and its plugins using pyinstaller, this path + helps pyinstaller to find the plugins. + + black_list:list + a list of module names that should be skipped. + + white_list:list + a list of modules that comes with your main module. If you have a + built-in module, the module name should be inserted into the list. + + For example, robot_cuisine is a built-in module inside robotchef. It + is listed in white_list. + """ + __plugin_name_patterns = "^%s.+$" % prefix + scan_plugins_regex( + plugin_name_patterns=__plugin_name_patterns, + pyinstaller_path=pyinstaller_path, + black_list=black_list, + white_list=white_list, + ) + + +def scan_plugins_regex( + plugin_name_patterns=None, + pyinstaller_path=None, + black_list=None, + white_list=None, +): + + """ + Implicitly discover plugins via pkgutil and pyinstaller path using + regular expression + + Parameters + ----------------- + + plugin_name_patterns: python regular expression + it is used to match all your plugins, either it is a prefix, + a suffix, some text in the middle or all. + + pyinstaller_path:string used in pyinstaller only. When your end developer would package your main library and its plugins using pyinstaller, this path helps pyinstaller to find the plugins. @@ -59,15 +108,20 @@ def scan_plugins(prefix, path, black_list=None, white_list=None): white_list = [] # scan pkgutil.iter_modules - module_names = (module_info[1] for module_info in pkgutil.iter_modules() - if module_info[2] and module_info[1].startswith(prefix)) + module_names = ( + module_info[1] + for module_info in pkgutil.iter_modules() + if module_info[2] and re.match(plugin_name_patterns, module_info[1]) + ) # scan pyinstaller - module_names_from_pyinstaller = scan_from_pyinstaller(prefix, path) + module_names_from_pyinstaller = scan_from_pyinstaller( + plugin_name_patterns, pyinstaller_path + ) - all_modules = chain(module_names, - module_names_from_pyinstaller, - white_list) + all_modules = chain( + module_names, module_names_from_pyinstaller, white_list + ) # loop through modules and find our plug ins for module_name in all_modules: @@ -89,15 +143,20 @@ def scan_plugins(prefix, path, black_list=None, white_list=None): # see: https://github.com/pyinstaller/pyinstaller/issues/1905 # load modules using iter_modules() # (should find all plug ins in normal build, but not pyinstaller) -def scan_from_pyinstaller(prefix, path): +def scan_from_pyinstaller(plugin_name_patterns, path): """ Discover plugins from pyinstaller """ table_of_content = set() - for a_toc in (importer.toc for importer in map(pkgutil.get_importer, path) - if hasattr(importer, 'toc')): + for a_toc in ( + importer.toc + for importer in map(pkgutil.get_importer, path) + if hasattr(importer, "toc") + ): table_of_content |= a_toc for module_name in table_of_content: - if module_name.startswith(prefix) and '.' not in module_name: + if "." in module_name: + continue + if re.match(plugin_name_patterns, module_name): yield module_name diff --git a/lml/plugin.py b/lml/plugin.py index dc8835c..35c6a26 100644 --- a/lml/plugin.py +++ b/lml/plugin.py @@ -93,9 +93,10 @@ class PluginInfo(object): echoing hey.. """ - def __init__(self, plugin_type, - abs_class_path=None, - tags=None, **keywords): + + def __init__( + self, plugin_type, abs_class_path=None, tags=None, **keywords + ): self.plugin_type = plugin_type self.absolute_import_path = abs_class_path self.cls = None @@ -103,9 +104,9 @@ class PluginInfo(object): self.__tags = tags def __getattr__(self, name): - if name == 'module_name': + if name == "module_name": if self.absolute_import_path: - module_name = self.absolute_import_path.split('.')[0] + module_name = self.absolute_import_path.split(".")[0] else: module_name = self.cls.__module__ return module_name @@ -124,8 +125,10 @@ class PluginInfo(object): yield tag def __repr__(self): - rep = {"plugin_type": self.plugin_type, - "path": self.absolute_import_path} + rep = { + "plugin_type": self.plugin_type, + "path": self.absolute_import_path, + } rep.update(self.properties) return json_dumps(rep) @@ -141,13 +144,14 @@ class PluginInfoChain(object): It is used in the plugin packages to list all plugin classes """ + def __init__(self, path): self._logger = logging.getLogger( - self.__class__.__module__ + '.' + self.__class__.__name__) + self.__class__.__module__ + "." + self.__class__.__name__ + ) self.module_name = path - def add_a_plugin(self, plugin_type, submodule=None, - **keywords): + def add_a_plugin(self, plugin_type, submodule=None, **keywords): """ Add a plain plugin @@ -161,9 +165,8 @@ class PluginInfoChain(object): the relative import path to your plugin class """ a_plugin_info = PluginInfo( - plugin_type, - self._get_abs_path(submodule), - **keywords) + plugin_type, self._get_abs_path(submodule), **keywords + ) self.add_a_plugin_instance(a_plugin_info) return self @@ -180,9 +183,11 @@ class PluginInfoChain(object): The developer has to specify the absolute import path """ - self._logger.debug("add %s as '%s' plugin", - plugin_info_instance.absolute_import_path, - plugin_info_instance.plugin_type) + self._logger.debug( + "add %s as '%s' plugin", + plugin_info_instance.absolute_import_path, + plugin_info_instance.plugin_type, + ) _load_me_later(plugin_info_instance) return self @@ -201,12 +206,14 @@ class PluginManager(object): the plugin type. All plugins of this plugin type will be registered to it. """ + def __init__(self, plugin_type): self.plugin_name = plugin_type self.registry = defaultdict(list) self.tag_groups = dict() self._logger = logging.getLogger( - self.__class__.__module__ + '.' + self.__class__.__name__) + self.__class__.__module__ + "." + self.__class__.__name__ + ) _register_class(self) def get_a_plugin(self, key, **keywords): @@ -237,8 +244,7 @@ class PluginManager(object): the key to find the plugin """ self._logger.debug(self.registry.keys()) - raise Exception( - "No %s is found for %s" % (self.plugin_name, key)) + raise Exception("No %s is found for %s" % (self.plugin_name, key)) def load_me_later(self, plugin_info): """ @@ -250,8 +256,7 @@ class PluginManager(object): plugin_info: a instance of plugin info """ - self._logger.debug('load %s later', - plugin_info.absolute_import_path) + self._logger.debug("load %s later", plugin_info.absolute_import_path) for key in plugin_info.tags(): self.registry[key.lower()].append(plugin_info) @@ -282,9 +287,7 @@ class PluginManager(object): else: # only library condition coud raise an exception raise Exception("%s is not installed" % library) - self._logger.debug("load %s now for '%s'", - cls, - key) + self._logger.debug("load %s now for '%s'", cls, key) return cls else: self.raise_exception(key) @@ -317,8 +320,7 @@ class PluginManager(object): plugin_info: a instance of plugin info """ - self._logger.debug("register %s", - _show_me_your_name(plugin_cls)) + self._logger.debug("register %s", _show_me_your_name(plugin_cls)) primary_tag = None for index, key in enumerate(plugin_info.tags()): plugin_info.cls = plugin_cls @@ -334,18 +336,21 @@ class PluginManager(object): def _register_class(cls): """Reigister a newly created plugin manager""" - log.debug("declare '%s' plugin manager", - cls.plugin_name) + log.debug("declare '%s' plugin manager", cls.plugin_name) PLUG_IN_MANAGERS[cls.plugin_name] = cls if cls.plugin_name in CACHED_PLUGIN_INFO: # check if there is early registrations or not for plugin_info in CACHED_PLUGIN_INFO[cls.plugin_name]: if plugin_info.absolute_import_path: - log.debug("load cached plugin info: %s", - plugin_info.absolute_import_path) + log.debug( + "load cached plugin info: %s", + plugin_info.absolute_import_path, + ) else: - log.debug("load cached plugin info: %s", - _show_me_your_name(plugin_info.cls)) + log.debug( + "load cached plugin info: %s", + _show_me_your_name(plugin_info.cls), + ) cls.load_me_later(plugin_info) del CACHED_PLUGIN_INFO[cls.plugin_name] @@ -358,8 +363,7 @@ def _register_a_plugin(plugin_info, plugin_cls): manager.register_a_plugin(plugin_cls, plugin_info) else: # let's cache it and wait the manager to be registered - log.debug("caching %s", - _show_me_your_name(plugin_cls.__name__)) + log.debug("caching %s", _show_me_your_name(plugin_cls.__name__)) CACHED_PLUGIN_INFO[plugin_info.plugin_type].append(plugin_info) @@ -370,17 +374,19 @@ def _load_me_later(plugin_info): manager.load_me_later(plugin_info) else: # let's cache it and wait the manager to be registered - log.debug("caching %s for %s", - plugin_info.absolute_import_path, - plugin_info.plugin_type) + log.debug( + "caching %s for %s", + plugin_info.absolute_import_path, + plugin_info.plugin_type, + ) CACHED_PLUGIN_INFO[plugin_info.plugin_type].append(plugin_info) def _get_me_pypi_package_name(module): try: module_name = module.__module__ - root_module_name = module_name.split('.')[0] - return root_module_name.replace('_', '-') + root_module_name = module_name.split(".")[0] + return root_module_name.replace("_", "-") except AttributeError: return None diff --git a/lml/utils.py b/lml/utils.py index 31caadd..647254d 100644 --- a/lml/utils.py +++ b/lml/utils.py @@ -20,14 +20,14 @@ class PythonObjectEncoder(JSONEncoder): """ Custom object encoder for json dump """ + def default(self, obj): - a_list_of_types = (list, dict, str, - int, float, bool, type(None)) + a_list_of_types = (list, dict, str, int, float, bool, type(None)) if PY2: a_list_of_types += (unicode,) if isinstance(obj, a_list_of_types): return JSONEncoder.default(self, obj) - return {'_python_object': str(obj)} + return {"_python_object": str(obj)} def json_dumps(keywords): @@ -41,8 +41,8 @@ def do_import(plugin_module_name): """dynamically import a module""" try: plugin_module = __import__(plugin_module_name) - if '.' in plugin_module_name: - modules = plugin_module_name.split('.') + if "." in plugin_module_name: + modules = plugin_module_name.split(".") for module in modules[1:]: plugin_module = getattr(plugin_module, module) log.debug("found " + plugin_module_name) @@ -55,9 +55,9 @@ def do_import(plugin_module_name): def do_import_class(plugin_class): """dynamically import a class""" try: - plugin_module_name = plugin_class.rsplit('.', 1)[0] + plugin_module_name = plugin_class.rsplit(".", 1)[0] plugin_module = __import__(plugin_module_name) - modules = plugin_class.split('.') + modules = plugin_class.split(".") for module in modules[1:]: plugin_module = getattr(plugin_module, module) return plugin_module diff --git a/setup.py b/setup.py index ce56e72..024eb63 100644 --- a/setup.py +++ b/setup.py @@ -11,14 +11,14 @@ PY26 = PY2 and sys.version_info[1] < 7 NAME = 'lml' AUTHOR = 'C.W.' -VERSION = '0.0.3' +VERSION = '0.0.4' EMAIL = 'wangc_2011@hotmail.com' LICENSE = 'New BSD' DESCRIPTION = ( 'Load me later. A lazy plugin management system.' ) URL = 'https://github.com/chfw/lml' -DOWNLOAD_URL = '%s/archive/0.0.3.tar.gz' % URL +DOWNLOAD_URL = '%s/archive/0.0.4.tar.gz' % URL FILES = ['README.rst', 'CHANGELOG.rst'] KEYWORDS = [ 'python' @@ -46,8 +46,8 @@ EXTRAS_REQUIRE = {} # You do not need to read beyond this line PUBLISH_COMMAND = '{0} setup.py sdist bdist_wheel upload -r pypi'.format( sys.executable) -GS_COMMAND = ('gs lml v0.0.3 ' + - "Find 0.0.3 in changelog for more details") +GS_COMMAND = ('gs lml v0.0.4 ' + + "Find 0.0.4 in changelog for more details") NO_GS_MESSAGE = ('Automatic github release is disabled. ' + 'Please install gease to enable it.') UPLOAD_FAILED_MSG = ( diff --git a/tests/sample_plugin/sample_plugin/__init__.py b/tests/sample_plugin/sample_plugin/__init__.py index a4133d0..e062d6b 100644 --- a/tests/sample_plugin/sample_plugin/__init__.py +++ b/tests/sample_plugin/sample_plugin/__init__.py @@ -1,5 +1,4 @@ from lml.registry import PluginInfoChain -__test_plugins__ = PluginInfoChain(__name__).add_a_plugin( - 'test_io2', 'reader') +__test_plugins__ = PluginInfoChain(__name__).add_a_plugin("test_io2", "reader") diff --git a/tests/sample_plugin/sample_plugin/manager.py b/tests/sample_plugin/sample_plugin/manager.py index 1013239..ee2114f 100644 --- a/tests/sample_plugin/sample_plugin/manager.py +++ b/tests/sample_plugin/sample_plugin/manager.py @@ -3,7 +3,7 @@ from lml.plugin import PluginManager class TestPluginManager(PluginManager): def __init__(self): - PluginManager.__init__(self, 'test_io2') + PluginManager.__init__(self, "test_io2") def load_me_later(self, plugin_info): PluginManager.load_me_later(self, plugin_info) diff --git a/tests/sample_plugin/setup.py b/tests/sample_plugin/setup.py index c3bac61..66eea94 100644 --- a/tests/sample_plugin/setup.py +++ b/tests/sample_plugin/setup.py @@ -9,27 +9,28 @@ try: from setuptools import setup, find_packages except ImportError: from ez_setup import use_setuptools + use_setuptools() from setuptools import setup, find_packages setup( - name='pyexcel-test_plugin2', + name="pyexcel-test_plugin2", author="C. W.", - version='0.0.1', + version="0.0.1", author_email="wangc_2011@hotmail.com", - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + packages=find_packages(exclude=["ez_setup", "examples", "tests"]), include_package_data=True, long_description=__doc__, zip_safe=False, classifiers=[ - 'Development Status :: 3 - Alpha', - 'Topic :: Office/Business', - 'Topic :: Utilities', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python', - 'License :: OSI Approved :: GNU General Public License v3', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7' - ] + "Development Status :: 3 - Alpha", + "Topic :: Office/Business", + "Topic :: Utilities", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python", + "License :: OSI Approved :: GNU General Public License v3", + "Intended Audience :: Developers", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + ], ) diff --git a/tests/test_plugin/pyexcel_test/__init__.py b/tests/test_plugin/pyexcel_test/__init__.py index 65e288d..e5c2055 100644 --- a/tests/test_plugin/pyexcel_test/__init__.py +++ b/tests/test_plugin/pyexcel_test/__init__.py @@ -1,5 +1,4 @@ from lml.plugin import PluginInfoChain -__test_plugins__ = PluginInfoChain(__name__).add_a_plugin( - 'test_io', 'x') +__test_plugins__ = PluginInfoChain(__name__).add_a_plugin("test_io", "x") diff --git a/tests/test_plugin/setup.py b/tests/test_plugin/setup.py index 2a3270a..a4b8b1c 100644 --- a/tests/test_plugin/setup.py +++ b/tests/test_plugin/setup.py @@ -9,27 +9,28 @@ try: from setuptools import setup, find_packages except ImportError: from ez_setup import use_setuptools + use_setuptools() from setuptools import setup, find_packages setup( - name='pyexcel-test_plugin', + name="pyexcel-test_plugin", author="C. W.", - version='0.0.1', + version="0.0.1", author_email="wangc_2011@hotmail.com", - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + packages=find_packages(exclude=["ez_setup", "examples", "tests"]), include_package_data=True, long_description=__doc__, zip_safe=False, classifiers=[ - 'Development Status :: 3 - Alpha', - 'Topic :: Office/Business', - 'Topic :: Utilities', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python', - 'License :: OSI Approved :: GNU General Public License v3', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7' - ] + "Development Status :: 3 - Alpha", + "Topic :: Office/Business", + "Topic :: Utilities", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python", + "License :: OSI Approved :: GNU General Public License v3", + "Intended Audience :: Developers", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + ], ) diff --git a/tests/test_plugin_info.py b/tests/test_plugin_info.py index 94dddf2..3efa39a 100644 --- a/tests/test_plugin_info.py +++ b/tests/test_plugin_info.py @@ -4,26 +4,26 @@ from nose.tools import eq_ def test_plugin_info(): - info = PluginInfo('renderer', - abs_class_path='good.plugin.path', - custom='property') - assert info.custom == 'property' + info = PluginInfo( + "renderer", abs_class_path="good.plugin.path", custom="property" + ) + assert info.custom == "property" keys = list(info.tags()) assert len(keys) == 1 - assert keys[0] == 'renderer' - assert info.module_name == 'good' - expected = {"path": "good.plugin.path", - "plugin_type": "renderer", - "custom": "property"} + assert keys[0] == "renderer" + assert info.module_name == "good" + expected = { + "path": "good.plugin.path", + "plugin_type": "renderer", + "custom": "property", + } eq_(json.loads(info.__repr__()), expected) def test_module_name_scenario_2(): - class TestClass2: pass - info = PluginInfo('renderer', - custom='property') + info = PluginInfo("renderer", custom="property") info.cls = TestClass2 - eq_(info.module_name, 'test_plugin_info') + eq_(info.module_name, "test_plugin_info") diff --git a/tests/test_plugin_loader.py b/tests/test_plugin_loader.py index 744ef83..356644b 100644 --- a/tests/test_plugin_loader.py +++ b/tests/test_plugin_loader.py @@ -2,86 +2,105 @@ from mock import patch from nose.tools import eq_ -@patch('pkgutil.get_importer') +@patch("pkgutil.get_importer") def test_load_from_pyinstaller(pkgutil_get_importer): - sample_toc = set(['pyexcel_io', 'pyexcel_xls', 'blah']) + sample_toc = set(["pyexcel_io", "pyexcel_xls", "blah", "test.dot.module"]) pkgutil_get_importer.return_value.toc = sample_toc from lml.loader import scan_from_pyinstaller - module_names = scan_from_pyinstaller('pyexcel_', 'path') - expected = ['pyexcel_io', 'pyexcel_xls'] + + module_names = scan_from_pyinstaller("pyexcel_", "path") + expected = ["pyexcel_io", "pyexcel_xls"] eq_(sorted(list(module_names)), sorted(expected)) -@patch('pkgutil.get_importer') -@patch('pkgutil.iter_modules') -def test_load_plugins(pkgutil_iter_modules, - pkgutil_get_importer): - test_module_name = 'pyexcel_test' - sample_toc = set(['pyexcel_io']) +@patch("pkgutil.get_importer") +def test_load_from_pyinstaller_with_regex(pkgutil_get_importer): + sample_toc = set(["pyexcel_io", "pyexcel_xls", "blah"]) pkgutil_get_importer.return_value.toc = sample_toc - pkgutil_iter_modules.return_value = [('not used', test_module_name, True)] + from lml.loader import scan_from_pyinstaller + + module_names = scan_from_pyinstaller("^.+cel_.+$", "path") + expected = ["pyexcel_io", "pyexcel_xls"] + eq_(sorted(list(module_names)), sorted(expected)) + + +@patch("pkgutil.get_importer") +@patch("pkgutil.iter_modules") +def test_load_plugins(pkgutil_iter_modules, pkgutil_get_importer): + test_module_name = "pyexcel_test" + sample_toc = set(["pyexcel_io"]) + pkgutil_get_importer.return_value.toc = sample_toc + pkgutil_iter_modules.return_value = [("not used", test_module_name, True)] from lml.loader import scan_plugins - scan_plugins('pyexcel_', '.', ['pyexcel_io']) + + scan_plugins("pyexcel_", ".", ["pyexcel_io"]) from lml.plugin import CACHED_PLUGIN_INFO - info = CACHED_PLUGIN_INFO['test_io'][0] - eq_(info.plugin_type, 'test_io') - eq_(info.absolute_import_path, 'pyexcel_test.x') + + info = CACHED_PLUGIN_INFO["test_io"][0] + eq_(info.plugin_type, "test_io") + eq_(info.absolute_import_path, "pyexcel_test.x") -@patch('pkgutil.get_importer') -@patch('pkgutil.iter_modules') -def test_load_plugins_without_pyinstaller(pkgutil_iter_modules, - pkgutil_get_importer): - test_module_name = 'pyexcel_test' +@patch("pkgutil.get_importer") +@patch("pkgutil.iter_modules") +def test_load_plugins_without_pyinstaller( + pkgutil_iter_modules, pkgutil_get_importer +): + test_module_name = "pyexcel_test" sample_toc = set() pkgutil_get_importer.return_value.toc = sample_toc # mock iter modules - pkgutil_iter_modules.return_value = [('not used', test_module_name, True)] + pkgutil_iter_modules.return_value = [("not used", test_module_name, True)] from lml.loader import scan_plugins - scan_plugins('pyexcel_', '.', ['pyexcel_io']) + + scan_plugins("pyexcel_", ".", ["pyexcel_io"]) from lml.plugin import CACHED_PLUGIN_INFO - info = CACHED_PLUGIN_INFO['test_io'][0] - eq_(info.plugin_type, 'test_io') - eq_(info.absolute_import_path, 'pyexcel_test.x') + + info = CACHED_PLUGIN_INFO["test_io"][0] + eq_(info.plugin_type, "test_io") + eq_(info.absolute_import_path, "pyexcel_test.x") -@patch('pkgutil.get_importer') -@patch('pkgutil.iter_modules') -@patch('lml.plugin._load_me_later') -def test_load_plugins_without_any_plugins(mocked_load_me_later, - pkgutil_iter_modules, - pkgutil_get_importer): +@patch("pkgutil.get_importer") +@patch("pkgutil.iter_modules") +@patch("lml.plugin._load_me_later") +def test_load_plugins_without_any_plugins( + mocked_load_me_later, pkgutil_iter_modules, pkgutil_get_importer +): sample_toc = set() pkgutil_get_importer.return_value.toc = sample_toc pkgutil_iter_modules.return_value = [] from lml.loader import scan_plugins - scan_plugins('pyexcel_', '.', ['pyexcel_io']) + + scan_plugins("pyexcel_", ".", ["pyexcel_io"]) assert mocked_load_me_later.called is False -@patch('pkgutil.get_importer') -@patch('pkgutil.iter_modules') -@patch('lml.plugin._load_me_later') -def test_load_plugins_without_black_list(mocked_load_me_later, - pkgutil_iter_modules, - pkgutil_get_importer): +@patch("pkgutil.get_importer") +@patch("pkgutil.iter_modules") +@patch("lml.plugin._load_me_later") +def test_load_plugins_without_black_list( + mocked_load_me_later, pkgutil_iter_modules, pkgutil_get_importer +): sample_toc = set() pkgutil_get_importer.return_value.toc = sample_toc pkgutil_iter_modules.return_value = [] from lml.loader import scan_plugins - scan_plugins('pyexcel_', '.') + + scan_plugins("pyexcel_", ".") assert mocked_load_me_later.called is False -@patch('pkgutil.get_importer') -@patch('pkgutil.iter_modules') -@patch('lml.plugin._load_me_later') -def test_load_plugins_import_error(mocked_load_me_later, - pkgutil_iter_modules, - pkgutil_get_importer): - sample_toc = set(['test_non_existent_module']) +@patch("pkgutil.get_importer") +@patch("pkgutil.iter_modules") +@patch("lml.plugin._load_me_later") +def test_load_plugins_import_error( + mocked_load_me_later, pkgutil_iter_modules, pkgutil_get_importer +): + sample_toc = set(["test_non_existent_module"]) pkgutil_get_importer.return_value.toc = sample_toc - pkgutil_iter_modules.return_value = [('not used', 'pyexcel_xls', False)] + pkgutil_iter_modules.return_value = [("not used", "pyexcel_xls", False)] from lml.loader import scan_plugins - scan_plugins('test_', '.', ['pyexcel_io']) + + scan_plugins("test_", ".", ["pyexcel_io"]) assert mocked_load_me_later.called is False diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index a49d2fa..0bcd1ce 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -6,24 +6,24 @@ from nose.tools import eq_, raises def test_plugin_manager(): - test_plugin = 'my plugin' + test_plugin = "my plugin" manager = PluginManager(test_plugin) assert PLUG_IN_MANAGERS[test_plugin] == manager def test_load_me_later(): - test_plugin = 'my plugin' + test_plugin = "my plugin" manager = PluginManager(test_plugin) plugin_info = make_me_a_plugin_info(test_plugin) manager.load_me_later(plugin_info) assert list(manager.registry.keys()) == [test_plugin] -@patch('lml.plugin.do_import_class') +@patch("lml.plugin.do_import_class") def test_load_me_now(mock_import): custom_class = PluginInfo mock_import.return_value = custom_class - test_plugin = 'my plugin' + test_plugin = "my plugin" manager = PluginManager(test_plugin) plugin_info = make_me_a_plugin_info(test_plugin) manager.load_me_later(plugin_info) @@ -32,27 +32,27 @@ def test_load_me_now(mock_import): @raises(Exception) -@patch('lml.plugin.do_import_class') +@patch("lml.plugin.do_import_class") def test_load_me_now_exception(mock_import): custom_class = PluginInfo mock_import.return_value = custom_class - test_plugin = 'my plugin' + test_plugin = "my plugin" manager = PluginManager(test_plugin) - plugin_info = make_me_a_plugin_info('my') + plugin_info = make_me_a_plugin_info("my") manager.load_me_later(plugin_info) - manager.load_me_now('my', 'my special library') + manager.load_me_now("my", "my special library") @raises(Exception) def test_load_me_now_no_key_found(): - test_plugin = 'my plugin' + test_plugin = "my plugin" manager = PluginManager(test_plugin) - manager.load_me_now('my', custom_property='here') + manager.load_me_now("my", custom_property="here") -@patch('lml.plugin.do_import_class') +@patch("lml.plugin.do_import_class") def test_dynamic_load_library(mock_import): - test_plugin = 'test plugin' + test_plugin = "test plugin" custom_obj = object() mock_import.return_value = custom_obj manager = PluginManager(test_plugin) @@ -61,9 +61,9 @@ def test_dynamic_load_library(mock_import): eq_(custom_obj, plugin_info.cls) -@patch('lml.plugin.do_import_class') +@patch("lml.plugin.do_import_class") def test_dynamic_load_library_no_action(mock_import): - test_plugin = 'test plugin' + test_plugin = "test plugin" manager = PluginManager(test_plugin) plugin_info = make_me_a_plugin_info(test_plugin) plugin_info.cls = object() @@ -76,37 +76,38 @@ class TestClass: def test_register_a_plugin(): - test_plugin = 'test plugin' + test_plugin = "test plugin" manager = PluginManager(test_plugin) - plugin_info = make_me_a_plugin_info('my') + plugin_info = make_me_a_plugin_info("my") manager.register_a_plugin(TestClass, plugin_info) eq_(plugin_info.cls, TestClass) - eq_(manager.registry['my'][0], plugin_info) + eq_(manager.registry["my"][0], plugin_info) def test_get_a_plugin(): - test_plugin = 'test plugin' + test_plugin = "test plugin" manager = PluginManager(test_plugin) - plugin_info = make_me_a_plugin_info('my') + plugin_info = make_me_a_plugin_info("my") plugin_info.cls = TestClass manager.register_a_plugin(TestClass, plugin_info) - the_plugin = manager.get_a_plugin('my') + the_plugin = manager.get_a_plugin("my") assert isinstance(the_plugin, TestClass) def test_register_class(): - test_plugin = 'test_plugin' - plugin_info = make_me_a_plugin_info('my') + test_plugin = "test_plugin" + plugin_info = make_me_a_plugin_info("my") CACHED_PLUGIN_INFO[test_plugin].append(plugin_info) manager = PluginManager(test_plugin) - assert list(manager.registry.keys()) == ['my'] + assert list(manager.registry.keys()) == ["my"] def test_load_me_later_function(): from lml.plugin import _load_me_later - test_plugin = 'my plugin' + + test_plugin = "my plugin" manager = PluginManager(test_plugin) plugin_info = make_me_a_plugin_info(test_plugin) _load_me_later(plugin_info) @@ -116,13 +117,14 @@ def test_load_me_later_function(): @raises(ImportError) def test_do_import_cls_error(): from lml.plugin import do_import_class + do_import_class("non.exist.class") def test_register_a_plugin_function_1(): PluginManager("test plugin") - @PluginInfo('test plugin', tags=['akey']) + @PluginInfo("test plugin", tags=["akey"]) class MyPlugin(object): pass @@ -130,9 +132,9 @@ def test_register_a_plugin_function_1(): def test_register_a_plugin_function_2(): - non_existent_plugin = 'I have no plugin manager' + non_existent_plugin = "I have no plugin manager" - @PluginInfo(non_existent_plugin, tags=['akey']) + @PluginInfo(non_existent_plugin, tags=["akey"]) class MyPlugin(object): pass @@ -143,35 +145,34 @@ def test_register_a_plugin_function_2(): def test_primary_key(): manager = PluginManager("test plugin2") - @PluginInfo('test plugin2', tags=['primary key', 'key 1', 'key 2']) + @PluginInfo("test plugin2", tags=["primary key", "key 1", "key 2"]) class MyPlugin(object): pass - pk = manager.get_primary_key('key 1') - eq_(pk, 'primary key') + pk = manager.get_primary_key("key 1") + eq_(pk, "primary key") def test_dict_as_plugin_payload(): manager = PluginManager("test plugin3") - plugin = PluginInfo('test plugin3', tags=['primary key', 'key 1', 'key 2']) + plugin = PluginInfo("test plugin3", tags=["primary key", "key 1", "key 2"]) plugin(dict(B=1)) - instance = manager.load_me_now('key 1') + instance = manager.load_me_now("key 1") eq_(instance, dict(B=1)) def test_show_me_your_name(): - class Test(object): pass name = _show_me_your_name(Test) - eq_(name, 'Test') + eq_(name, "Test") name2 = _show_me_your_name(dict(A=1)) - assert 'dict' in name2 + assert "dict" in name2 def make_me_a_plugin_info(plugin_name): - return PluginInfo(plugin_name, 'abs_path', custom='property') + return PluginInfo(plugin_name, "abs_path", custom="property") diff --git a/tests/test_utils.py b/tests/test_utils.py index dd226f7..4e25539 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,7 +5,6 @@ from lml.utils import do_import def test_json_dumps(): - class TestClass: pass @@ -16,12 +15,14 @@ def test_json_dumps(): def test_do_import(): import pyexcel_test + pyexcel_test_package = do_import("pyexcel_test") eq_(pyexcel_test_package, pyexcel_test) def test_do_import_2(): import lml.plugin as plugin + themodule = do_import("lml.plugin") eq_(plugin, themodule) @@ -33,5 +34,6 @@ def test_do_import_error(): def test_do_import_cls(): from lml.utils import do_import_class + manager = do_import_class("lml.plugin.PluginManager") eq_(manager, PluginManager)