pax_global_header00006660000000000000000000000064126545417520014525gustar00rootroot0000000000000052 comment=4753ec5b7d8897fa60c0560c2d3a2d43a97dee70 straight.plugin-1.4.1/000077500000000000000000000000001265454175200146525ustar00rootroot00000000000000straight.plugin-1.4.1/.gitignore000066400000000000000000000000171265454175200166400ustar00rootroot00000000000000*.py[co] *.swp straight.plugin-1.4.1/AUTHORS000066400000000000000000000003771265454175200157310ustar00rootroot00000000000000Calvin Spealman ironfroggy@gmail.com @ironfroggy http://github.com/ironfroggy Dustin Lacewell dlacewell@gmail.com @dustinlacewell http://github.com/dustinlacewell Erik Youngren artanis.00@gmail.com http://artanis00.blogspot.com/ http://github.com/Artanis straight.plugin-1.4.1/LICENSE000066400000000000000000000020751265454175200156630ustar00rootroot00000000000000Copyright (C) 2011 by Calvin Spealman (ironfroggy@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. straight.plugin-1.4.1/MANIFEST.in000066400000000000000000000001421265454175200164050ustar00rootroot00000000000000include README.rst include LICENSE include AUTHORS include tests.py graft test-packages graft doc straight.plugin-1.4.1/README.rst000066400000000000000000000030331265454175200163400ustar00rootroot00000000000000Full Documentation: http://readthedocs.org/docs/straightplugin/ Mailing List: https://groups.google.com/forum/#!forum/straight.plugin Straight Plugin is very easy. Straight Plugin provides a type of plugin you can create from almost any existing Python modules, and an easy way for outside developers to add functionality and customization to your projects with their own plugins. Using any available plugins is a snap. :: from straight.plugin import load plugins = load('theproject.plugins', subclasses=FileHandler) handlers = plugins.produce() for line in open(filename): print handlers.pipe(line) And, writing plugins is just as easy. :: from theproject import FileHandler class LineNumbers(FileHandler): def __init__(self): self.lineno = 0 def pipe(line): self.lineno += 1 return "%04d %s" % (self.lineno, line) Plugins are found from a :term:`namespace`, which means the above example would find any ``FileHandler`` classes defined in modules you might import as ``theproject.plugins.default`` or ``theproject.plugins.extra``. Through the magic of :term:`namespace packages `, we can even split these up into separate installations, even managed by different teams. This means you can ship a project with a set of default plugins implementing its behavior, and allow other projects to hook in new functionality simply by shipping their own plugins under the same :term:`namespace`. :doc:`Get started and learn more, today ` straight.plugin-1.4.1/docs/000077500000000000000000000000001265454175200156025ustar00rootroot00000000000000straight.plugin-1.4.1/docs/Makefile000066400000000000000000000127341265454175200172510ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/straightplugin.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/straightplugin.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/straightplugin" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/straightplugin" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." straight.plugin-1.4.1/docs/api.rst000066400000000000000000000007171265454175200171120ustar00rootroot00000000000000Straight Plugin API =================== Loaders ####### .. autofunction:: straight.plugin.loaders.unified_load .. autoclass:: straight.plugin.loaders.Loader .. autoclass:: straight.plugin.loaders.ModuleLoader .. autoclass:: straight.plugin.loaders.ObjectLoader .. autoclass:: straight.plugin.loaders.ClassLoader .. _api-plugin-manager: PluginManager ############# .. autoclass:: straight.plugin.manager.PluginManager :members: produce, call, first, pipe, straight.plugin-1.4.1/docs/conf.py000066400000000000000000000172361265454175200171120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # straight.plugin documentation build configuration file, created by # sphinx-quickstart on Wed Jan 25 22:49:22 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # 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. #sys.path.insert(0, os.path.abspath('.')) # -- 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', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'straight.plugin' copyright = u'2012, Calvin Spealman' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.4' # The full version, including alpha/beta/rc tags. release = '1.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- 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 = 'default' # 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 themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # 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'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'straightplugindoc' # -- 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': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'straightplugin.tex', u'straight.plugin Documentation', u'Calvin Spealman', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'straightplugin', u'straight.plugin Documentation', [u'Calvin Spealman'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- 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 = [ ('index', 'straightplugin', u'straight.plugin Documentation', u'Calvin Spealman', 'straightplugin', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' straight.plugin-1.4.1/docs/getting-started.rst000066400000000000000000000065371265454175200214540ustar00rootroot00000000000000Getting Started =============== Install ^^^^^^^ :: pip install straight.plugin That was super easy. Decide on a Namespace ^^^^^^^^^^^^^^^^^^^^^ You'll want to decide on a :term:`namespace` within your package where you'll keep your own plugins and where other developers can add more plugins for your package to use. For example, if you're writing a log filtering library named ``logfilter`` you may choose ``logfilter.plugins`` as a package to hold your plugins, so you'll create the empty package as you would any other python package. However, the only contents of ``logfilter/plugins/__init__.py`` will be a little bit of special code telling python this is a :term:`namespace package`. :: # This file will not be needed in Python 3.3 from pkgutil import extend_path __path__ = extend_path(__path__, __name__) Now, any modules you place in this package are plugin modules able to be loaded by ``straight.plugin``. :: from straight.plugin import load plugins = load("logfilter.plugins") If you'll be using more plugins than writing them, you should :doc:`read more ` about the loaders available and how they work. Write a Plugin ^^^^^^^^^^^^^^ Writing a plugin is even easier than loading them. There are two important plugin types to learn: Module plugins and class Plugins. Every module in your :term:`namespace package` is a module plugin. Every class they define is a class plugin. When you load module plugins, you get all of them. When you load class plugins, you filter them by a common base and only get those class plugins which inherit it. Module plugins are simple and usually define a few functions with names expected by whoever is loading and using the plugins. :: # This is a module plugin def add_extra(data): if 'x' in data and 'y' in data: data['z'] = x * y # This was a fairly useless plugin Class plugins are only a little longer, but can be a bit more controlled to work with. They depend on a common class the plugins inherit, and this would be defined by the project loading and using the plugins. :: # This is a class plugin class RstContentParser(ContentPlugin): """Parses any .rst files in a bundle.""" extensions = ('.rst',) def parse(self, content_file): src = content_file.read() return self.parse_string(src) def parse_string(self, src): parts = publish_parts(source=src, writer_name='html') return parts['html_body'] You can fit as many class plugins inside a module plugin as you want, and to load them instead of the modules you simply pass a ``subclasses`` parameter to ``load()``. :: from straight.plugin import load plugins = load("jules.plugins", subclasses=ContentPlugin) The resulting set of plugins are all the classes found which inherit from ContentPlugin. You can do whatever you want with these, but there are some helpful tools to make it easier to work with Class plugins. You can easily create instances of all the classes, which gives you a set of Instance plugins. :: instances = plugins.produce() You can even pass initialization parameters to ``produce()`` and they'll be used when creating instances of all the classes. You can see the :ref:`API docs ` for the ``PluginManager`` to see the other ways you can work with groups of plugins. straight.plugin-1.4.1/docs/glossary.rst000066400000000000000000000024501265454175200202000ustar00rootroot00000000000000Glossary ------------- .. glossary:: :sorted: package A Python package is a module defined by a directory, containing a ``__init__.py`` file, and can contain other modules or other packages within it. :: package/ __init__.py subpackage/ __init__.py submodule.py see also, :term:`namespace package` distribution Separately installable sets of Python modules as stored in the Python package index, and installed by distutils or setuptools. *definition taken from* `PEP 382`_ *text* vendor package Groups of files installed by an operating system's packaging mechanism (e.g. Debian or Redhat packages install on Linux systems). *definition taken from* `PEP 382`_ *text* namespace package Mechanism for splitting a single Python package across multiple directories on disk. One or more distributions (see :term:`distribution`) may provide modules which exist inside the same :term:`namespace package`. *definition taken from* `PEP 382`_ *text* module An importable python namespace defined in a single file. .. _PEP 382: http://www.python.org/dev/peps/pep-0382/ straight.plugin-1.4.1/docs/index.rst000066400000000000000000000042271265454175200174500ustar00rootroot00000000000000.. straight.plugin documentation master file, created by sphinx-quickstart on Wed Jan 25 22:49:22 2012. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to straight.plugin's documentation! =========================================== .. toctree:: :maxdepth: 1 Getting Started Writing Plugins Using Plugins API Glossary Overview ======== Straight Plugin is very easy. Straight Plugin provides a type of plugin you can create from almost any existing Python modules, and an easy way for outside developers to add functionality and customization to your projects with their own plugins. Using any available plugins is a snap. :: from straight.plugin import load plugins = load('theproject.plugins', subclasses=FileHandler) handlers = plugins.produce() for line in open(filename): print handlers.pipe(line) And, writing plugins is just as easy. :: from theproject import FileHandler class LineNumbers(FileHandler): def __init__(self): self.lineno = 0 def pipe(line): self.lineno += 1 return "%04d %s" % (self.lineno, line) Plugins are found from a :term:`namespace`, which means the above example would find any ``FileHandler`` classes defined in modules you might import as ``theproject.plugins.default`` or ``theproject.plugins.extra``. Through the magic of :term:`namespace packages `, we can even split these up into separate installations, even managed by different teams. This means you can ship a project with a set of default plugins implementing its behavior, and allow other projects to hook in new functionality simply by shipping their own plugins under the same :term:`namespace`. :doc:`Get started and learn more, today ` More Resources ############## * Full Documentation: http://readthedocs.org/docs/straightplugin/ * Mailing List: https://groups.google.com/forum/#!forum/straight.plugin Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` straight.plugin-1.4.1/docs/loaders.rst000066400000000000000000000043411265454175200177670ustar00rootroot00000000000000Plugin Loaders ============== Currently, three simple loaders are provided. * The :ref:`ModuleLoader ` simply loads the modules found * The :ref:`ClassLoader ` loads the subclasses of a given type * The :ref:`ObjectLoader ` loads arbitrary objects from the modules .. _classloader: ClassLoader ----------- The recommended loader is the ``ClassLoader``, used to load all the classes from all of the modules in the namespace given. Optionally, you can pass a ``subclasses`` parameter to ``load()``, which will filter the loaded classes to those which are a sub-class of any given type. For example, :: import os from straight.plugin.loaders import ClassLoader from myapp import FileHandler plugins = ClassLoader().load('myplugins', subclasses=FileHandler) for filename in os.listdir('.'): for handler_cls in plugins: handler = handler_cls(filename) if handler.valid(): handler.process() However, it is preferred that you use the ``load()`` helper provided. :: from straight.plugin import load plugins = load('myplugins', subclasses=FileHandler) This will automatically use the ``ClassLoader`` when given a ``subclasses`` argument. .. _moduleloader: ModuleLoader ------------ Before anything else, ``straight.plugin`` loads modules. The ``ModuleLoader`` is used to do this. :: from straight.plugin.loaders import ModuleLoader plugins = ModuleLoader().load('myplugins') A note about `PEP-420 `_: Python 3.3 will support a new type of package, the Namespace Package. This allows language-level support for the namespaces that make ``straight.plugin`` work and when 3.3 lands, you can create addition plugins to be found in a namespace. For now, continue to use the ``pkgutil`` boilerplate, but when 3.3 is released, ``straight.plugin`` already supports both forms of namespace package! .. _objectloader: ObjectLoader ------------ If you need to combine multiple plugins inside each module, you can load all the objects from the modules, rather than the modules themselves. :: from straight.plugin.loaders import ObjectLoader plugins = ObjectLoader().load('myplugins') straight.plugin-1.4.1/docs/make.bat000066400000000000000000000117701265454175200172150ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\straightplugin.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\straightplugin.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end straight.plugin-1.4.1/docs/write-plugin.rst000066400000000000000000000047301265454175200207660ustar00rootroot00000000000000Writing Plugins =============== Plugins can exist inside your existing packages or in special namespace packages, which exist only to house plugins. The only requirement is that any package containing plugins be designated a "namespace package", which is currently performed in Python via the ``pkgutil.extend_path`` utility, seen below. This allows the namespace to be provided in multiple places on the python ``sys.path``, where ``import`` looks, and all the contents will be combined. Use a :term:`namespace package` This allows multiple packages installed on your system to share this name, so they may come from different installed projects and all combine to provide a larger set of plugins. Example ------- :: # logfilter/__init__.py from pkgutil import extend_path __path__ = extend_path(__path__, __name__) :: # logfilter/hide_extra.py from logfilter import Skip def filter(log_entry): level = log_entry.split(':', 1)[0] if level != 'EXTRA': return log_entry else: raise Skip() Using the plugin '''''''''''''''' In our log tool, we might load all the modules in the ``logfilter`` namespace, and then use them all to process each entry in our logs. We don't need to know all the filters ahead of time, and other packages can be installed on a user's system providing additional modules in the namespace, which we never even knew about. :: from straight.plugin import load class Skip(Exception): pass plugins = load('logfilter') def filter_entry(log_entry): for plugin in plugins: try: log_entry = plugin.filter(log_entry) except Skip: pass return log_entry Distributing Plugins '''''''''''''''''''' If you are writing plugins inside your own project to use, they'll be distributed like any other modules in your package. There is no extra work to do here. However, if you want to release and distribute plugins on their own, you'll need to tell your :term:`setup.py` about your :term:`namespace package`. :: setup( # ... namespace_packages = ['logfilter.plugins'] ) This will make sure when your plugins are installed alongside the original project, both are importable, even though they came from their own distributions. You can read more about this at the Distribute `documentation on namespace packages `_. straight.plugin-1.4.1/requirements-dev.txt000066400000000000000000000000071265454175200207070ustar00rootroot00000000000000sphinx straight.plugin-1.4.1/setup.py000066400000000000000000000012411265454175200163620ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup, find_packages INSTALL_REQUIRES = [] try: import importlib except ImportError: INSTALL_REQUIRES.append('importlib') setup(name='straight.plugin', version='1.4.1', description='A simple namespaced plugin facility', author='Calvin Spealman', author_email='ironfroggy@gmail.com', url='https://github.com/ironfroggy/straight.plugin', packages=find_packages(), namespace_packages=['straight'], install_requires=INSTALL_REQUIRES, classifiers=[ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Environment :: Plugins', ] ) straight.plugin-1.4.1/straight/000077500000000000000000000000001265454175200164775ustar00rootroot00000000000000straight.plugin-1.4.1/straight/__init__.py000066400000000000000000000000701265454175200206050ustar00rootroot00000000000000__import__('pkg_resources').declare_namespace(__name__) straight.plugin-1.4.1/straight/plugin/000077500000000000000000000000001265454175200177755ustar00rootroot00000000000000straight.plugin-1.4.1/straight/plugin/__init__.py000066400000000000000000000001021265454175200220770ustar00rootroot00000000000000from straight.plugin import loaders load = loaders.unified_load straight.plugin-1.4.1/straight/plugin/loaders.py000066400000000000000000000127641265454175200220120ustar00rootroot00000000000000"""Facility to load plugins.""" import sys import os from importlib import import_module from imp import find_module from straight.plugin.manager import PluginManager class Loader(object): """Base loader class. Only used as a base-class for other loaders.""" def __init__(self, *args, **kwargs): self._cache = [] def load(self, *args, **kwargs): self._fill_cache(*args, **kwargs) self._post_fill() self._order() return PluginManager(self._cache) def _meta(self, plugin): meta = getattr(plugin, '__plugin__', None) return meta def _post_fill(self): for plugin in self._cache: meta = self._meta(plugin) if not getattr(meta, 'load', True): self._cache.remove(plugin) for implied_namespace in getattr(meta, 'imply_plugins', []): plugins = self._cache self._cache = self.load(implied_namespace) self._post_fill() combined = [] combined.extend(plugins) combined.extend(self._cache) self._cache = combined def _order(self): self._cache.sort(key=self._plugin_priority, reverse=True) def _plugin_priority(self, plugin): meta = self._meta(plugin) return getattr(meta, 'priority', 0.0) class ModuleLoader(Loader): """Performs the work of locating and loading straight plugins. This looks for plugins in every location in the import path. """ def __init__(self, recurse=False): super(ModuleLoader, self).__init__() self.recurse = recurse def _isPackage(self, path): pkg_init = os.path.join(path, '__init__.py') if os.path.exists(pkg_init): return True return False def _findPluginFilePaths(self, namespace): already_seen = set() # Look in each location in the path for path in sys.path: # Within this, we want to look for a package for the namespace namespace_rel_path = namespace.replace(".", os.path.sep) namespace_path = os.path.join(path, namespace_rel_path) if os.path.exists(namespace_path): for possible in os.listdir(namespace_path): poss_path = os.path.join(namespace_path, possible) if os.path.isdir(poss_path): if not self._isPackage(poss_path): continue if self.recurse: subns = '.'.join((namespace, possible.split('.py')[0])) for path in self._findPluginFilePaths(subns): yield path base = possible else: base, ext = os.path.splitext(possible) if base == '__init__' or ext != '.py': continue if base not in already_seen: already_seen.add(base) yield os.path.join(namespace, possible) def _findPluginModules(self, namespace): for filepath in self._findPluginFilePaths(namespace): path_segments = list(filepath.split(os.path.sep)) path_segments = [p for p in path_segments if p] path_segments[-1] = os.path.splitext(path_segments[-1])[0] import_path = '.'.join(path_segments) try: module = import_module(import_path) except ImportError: #raise Exception(import_path) module = None if module is not None: yield module def _fill_cache(self, namespace): """Load all modules found in a namespace""" modules = self._findPluginModules(namespace) self._cache = list(modules) class ObjectLoader(Loader): """Loads classes or objects out of modules in a namespace, based on a provided criteria. The load() method returns all objects exported by the module. """ def __init__(self, recurse=False): self.module_loader = ModuleLoader(recurse=recurse) def _fill_cache(self, namespace): modules = self.module_loader.load(namespace) objects = [] for module in modules: for attr_name in dir(module): if not attr_name.startswith('_'): objects.append(getattr(module, attr_name)) self._cache = objects return objects class ClassLoader(ObjectLoader): """Loads classes out of plugin modules which are subclasses of a single given base class. """ def _fill_cache(self, namespace, subclasses=None): objects = super(ClassLoader, self)._fill_cache(namespace) classes = [] for cls in objects: if isinstance(cls, type): if subclasses is None: classes.append(cls) elif issubclass(cls, subclasses) and cls is not subclasses: classes.append(cls) self._cache = classes return classes def unified_load(namespace, subclasses=None, recurse=False): """Provides a unified interface to both the module and class loaders, finding modules by default or classes if given a ``subclasses`` parameter. """ if subclasses is not None: return ClassLoader(recurse=recurse).load(namespace, subclasses=subclasses) else: return ModuleLoader(recurse=recurse).load(namespace) straight.plugin-1.4.1/straight/plugin/manager.py000066400000000000000000000035261265454175200217670ustar00rootroot00000000000000class PluginManager(object): def __init__(self, plugins): self._plugins = plugins def __iter__(self): return iter(self._plugins) def __len__(self): return len(self._plugins) def __getitem__(self, index): return self._plugins[index] def produce(self, *args, **kwargs): """Produce a new set of plugins, treating the current set as plugin factories. """ new_plugins = [] for p in self._plugins: r = p(*args, **kwargs) new_plugins.append(r) return PluginManager(new_plugins) def call(self, methodname, *args, **kwargs): """Call a common method on all the plugins, if it exists.""" for plugin in self._plugins: method = getattr(plugin, methodname, None) if method is None: continue yield method(*args, **kwargs) def first(self, methodname, *args, **kwargs): """Call a common method on all the plugins, if it exists. Return the first result (the first non-None) """ for r in self.call(methodname, *args, **kwargs): if r is not None: return r raise ValueError("No plugins returned a non-None value") def pipe(self, methodname, first_arg, *args, **kwargs): """Call a common method on all the plugins, if it exists. The return value of each call becomes the replaces the first argument in the given argument list to pass to the next. Useful to utilize plugins as sets of filters. """ for plugin in self._plugins: method = getattr(plugin, methodname, None) if method is None: continue r = method(first_arg, *args, **kwargs) if r is not None: first_arg = r return r straight.plugin-1.4.1/test-packages/000077500000000000000000000000001265454175200174055ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/class-test-plugins/000077500000000000000000000000001265454175200231465ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/class-test-plugins/testplugin/000077500000000000000000000000001265454175200253445ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/class-test-plugins/testplugin/__init__.py000066400000000000000000000001131265454175200274500ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/class-test-plugins/testplugin/testclasses.py000066400000000000000000000002241265454175200302510ustar00rootroot00000000000000class A(object): class __plugin__: priority = 0.5 class B(object): class __plugin__: priority = 1.0 class A1(A): pass straight.plugin-1.4.1/test-packages/imply-plugins/000077500000000000000000000000001265454175200222165ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/imply-plugins/testplugin/000077500000000000000000000000001265454175200244145ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/imply-plugins/testplugin/__init__.py000066400000000000000000000001131265454175200265200ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/imply-plugins/testplugin/foo.py000066400000000000000000000001631265454175200255510ustar00rootroot00000000000000class __plugin__: imply_plugins = ( 'testplugin_2', ) load = False def do(x): return x + 1 straight.plugin-1.4.1/test-packages/imply-plugins/testplugin_2/000077500000000000000000000000001265454175200246355ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/imply-plugins/testplugin_2/__init__.py000066400000000000000000000001131265454175200267410ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/imply-plugins/testplugin_2/bar.py000066400000000000000000000000341265454175200257500ustar00rootroot00000000000000def do(x): return x + 1 straight.plugin-1.4.1/test-packages/more-test-plugins/000077500000000000000000000000001265454175200230035ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/more-test-plugins/testplugin/000077500000000000000000000000001265454175200252015ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/more-test-plugins/testplugin/__init__.py000066400000000000000000000001131265454175200273050ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/more-test-plugins/testplugin/bar.py000066400000000000000000000000341265454175200263140ustar00rootroot00000000000000def do(x): return x + 1 straight.plugin-1.4.1/test-packages/package-test-plugins/000077500000000000000000000000001265454175200234345ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/000077500000000000000000000000001265454175200256325ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/__init__.py000066400000000000000000000001131265454175200277360ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/bar/000077500000000000000000000000001265454175200263765ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/bar/__init__.py000066400000000000000000000000311265454175200305010ustar00rootroot00000000000000def do(i): return i+1straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/baz/000077500000000000000000000000001265454175200264065ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/baz/__init__.py000066400000000000000000000000321265454175200305120ustar00rootroot00000000000000def do(i): return i+3 straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/baz/quu/000077500000000000000000000000001265454175200272205ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/baz/quu/__init__.py000066400000000000000000000000341265454175200313260ustar00rootroot00000000000000def do(i): return "quu" straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/foo/000077500000000000000000000000001265454175200264155ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/package-test-plugins/testplugin/foo/__init__.py000066400000000000000000000000311265454175200305200ustar00rootroot00000000000000def do(i): return i+2straight.plugin-1.4.1/test-packages/pep-420-plugins/000077500000000000000000000000001265454175200221535ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/pep-420-plugins/testplugin/000077500000000000000000000000001265454175200243515ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/pep-420-plugins/testplugin/foo.py000066400000000000000000000000521265454175200255030ustar00rootroot00000000000000def do(x): return x + " from pep-420" straight.plugin-1.4.1/test-packages/some-test-plugins/000077500000000000000000000000001265454175200230045ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/some-test-plugins/testplugin/000077500000000000000000000000001265454175200252025ustar00rootroot00000000000000straight.plugin-1.4.1/test-packages/some-test-plugins/testplugin/__init__.py000066400000000000000000000001131265454175200273060ustar00rootroot00000000000000from pkgutil import extend_path __path__ = extend_path(__path__, __name__) straight.plugin-1.4.1/test-packages/some-test-plugins/testplugin/foo.py000066400000000000000000000000341265454175200263340ustar00rootroot00000000000000def do(x): return x + 1 straight.plugin-1.4.1/tests.py000077500000000000000000000175501265454175200164010ustar00rootroot00000000000000#!/usr/bin/env python import sys import os import unittest from types import ModuleType import mock from straight.plugin import loaders, manager try: skipIf = unittest.skipIf except AttributeError: import functools class SkipTest(Exception): """ Raise this exception in a test to skip it. Usually you can use TestResult.skip() or one of the skipping decorators instead of raising this directly. """ def skipIf(cond, reason): """ Unconditionally skip a test. """ def decorator(test_item): if cond: if not isinstance(test_item, type): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): pass test_item = skip_wrapper test_item.__unittest_skip__ = True test_item.__unittest_skip_why__ = reason return test_item else: return test_item return decorator class LoaderTestCaseMixin(object): paths = [] def setUp(self): for path in self.paths: if isinstance(path, tuple): path = os.path.join(*path) sys.path.append(path) super(LoaderTestCaseMixin, self).setUp() def tearDown(self): for path in self.paths: del sys.path[-1] for modname in list(sys.modules): if modname.startswith('testplugin'): del sys.modules[modname] class ModuleLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'more-test-plugins'), os.path.join(os.path.dirname(__file__), 'test-packages', 'some-test-plugins'), ) def setUp(self): self.loader = loaders.ModuleLoader() super(ModuleLoaderTestCase, self).setUp() def test_load(self): modules = list(self.loader.load('testplugin')) assert len(modules) == 2, modules def test_plugin(self): assert self.loader.load('testplugin')[0].do(1) == 2 class ImpliedNamespaceModuleTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'pep-420-plugins'), ) def setUp(self): self.loader = loaders.ModuleLoader() super(ImpliedNamespaceModuleTestCase, self).setUp() @skipIf(sys.version_info < (3, 3),"Python < 3.3") def test_load(self): modules = list(self.loader.load('testplugin')) assert len(modules) == 1, modules @skipIf(sys.version_info < (3, 3), "Python < 3.3") def test_plugin(self): r = self.loader.load('testplugin')[0].do("implied namespace packages are") self.assertEqual(r, "implied namespace packages are from pep-420") class ImplyLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'imply-plugins'), ) def setUp(self): self.loader = loaders.ModuleLoader() super(ImplyLoaderTestCase, self).setUp() def test_load(self): modules = list(self.loader.load('testplugin')) assert len(modules) == 1, modules assert modules[0].__name__ == 'testplugin_2.bar', modules[0].__name__ class ObjectLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'more-test-plugins'), os.path.join(os.path.dirname(__file__), 'test-packages', 'some-test-plugins'), ) def setUp(self): self.loader = loaders.ObjectLoader() super(ObjectLoaderTestCase, self).setUp() def test_load_all(self): objects = list(self.loader.load('testplugin')) self.assertEqual(len(objects), 2, str(objects)[:100] + ' ...') class ClassLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'class-test-plugins'), ) def setUp(self): self.loader = loaders.ClassLoader() super(ClassLoaderTestCase, self).setUp() def test_all_classes(self): classes = list(self.loader.load('testplugin')) self.assertEqual(len(classes), 3) def test_subclasses(self): from testplugin import testclasses classes = list(self.loader.load('testplugin', subclasses=testclasses.A)) self.assertEqual(len(classes), 1) self.assertTrue(classes[0] is testclasses.A1) class PriorityLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'class-test-plugins'), ) def setUp(self): self.loader = loaders.ClassLoader() super(PriorityLoaderTestCase, self).setUp() def test_all_classes(self): classes = list(self.loader.load('testplugin')) self.assertEqual(classes[0].__name__, 'B') self.assertEqual(classes[1].__name__, 'A') self.assertEqual(classes[2].__name__, 'A1') class PackageLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'package-test-plugins'), ) def setUp(self): self.loader = loaders.ModuleLoader() super(PackageLoaderTestCase, self).setUp() def test_find_packages(self): filepaths = list(self.loader._findPluginFilePaths('testplugin')) self.assertEqual(len(filepaths), 3) def test_load_packages(self): packages = list(self.loader.load('testplugin')) self.assertEqual(len(packages), 3) for pkg in packages: self.assertTrue(isinstance(pkg, ModuleType)) def test_plugin(self): plugins = self.loader.load('testplugin') results = set(p.do(1) for p in plugins) self.assertEqual(results, set((2, 3, 4))) class RecursingPackageLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): paths = ( os.path.join(os.path.dirname(__file__), 'test-packages', 'package-test-plugins'), ) def setUp(self): self.loader = loaders.ModuleLoader(recurse=True) super(RecursingPackageLoaderTestCase, self).setUp() def test_find_packages(self): filepaths = list(self.loader._findPluginFilePaths('testplugin')) self.assertEqual(len(filepaths), 4) def test_load_packages(self): packages = list(self.loader.load('testplugin')) self.assertEqual(len(packages), 4) for pkg in packages: self.assertTrue(isinstance(pkg, ModuleType)) def test_plugin(self): plugins = self.loader.load('testplugin') results = set(p.do(1) for p in plugins) self.assertEqual(results, set((2, 3, 4, 'quu'))) class PluginManagerTestCase(unittest.TestCase): def setUp(self): self.m = manager.PluginManager([ mock.Mock(), mock.Mock(), ]) def test_first(self): self.m._plugins[0].x.return_value = 1 self.assertEqual(1, self.m.first('x', 'a')) self.assertFalse(self.m._plugins[1].called) self.assertTrue(self.m._plugins[0].called_with('a')) def test_pipe(self): def plus_one(x): return x + 1 self.m._plugins[0].x.side_effect = plus_one self.m._plugins[1].x.side_effect = plus_one self.assertEqual(3, self.m.pipe('x', 1)) def test_call(self): results = self.m.call('x', 1) self.assertTrue(self.m._plugins[0].called_with('a')) self.assertTrue(self.m._plugins[1].x.called_with(1)) def test_produce(self): products = self.m.produce(1, 2) assert products[0] is self.m._plugins[0].return_value self.m._plugins[0].called_with(1, 2) assert products[1] is self.m._plugins[1].return_value self.m._plugins[1].called_with(1, 2) if __name__ == '__main__': unittest.main() straight.plugin-1.4.1/tox.ini000066400000000000000000000002221265454175200161610ustar00rootroot00000000000000[tox] envlist = py27,py33,py34,py35 [default] deps = [testenv] setenv = PYTHON_PATH = {toxinidir} commands = {envpython} tests.py {posargs}