pluginbase-0.5/0000775000175000017500000000000013005211740014646 5ustar steinersteiner00000000000000pluginbase-0.5/README0000664000175000017500000000122012760017765015543 0ustar steinersteiner00000000000000 { pluginbase } PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Step 1: from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') Step 2: plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) Step 3: with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() Or alternatively: my_plugin = plugin_source.load_plugin('my_plugin') my_plugin.do_something_cool() pluginbase-0.5/LICENSE0000664000175000017500000000272512760017765015703 0ustar steinersteiner00000000000000Copyright (c) 2014 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pluginbase-0.5/PKG-INFO0000664000175000017500000000147513005211740015752 0ustar steinersteiner00000000000000Metadata-Version: 1.1 Name: pluginbase Version: 0.5 Summary: A support library for building plugins sytems in Python. Home-page: http://github.com/mitsuhiko/pluginbase Author: Armin Ronacher Author-email: armin.ronacher@active-4.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers pluginbase-0.5/setup.py0000664000175000017500000000167313005211566016375 0ustar steinersteiner00000000000000try: from setuptools import setup except ImportError: from distutils.core import setup setup( name='pluginbase', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', version='0.5', url='http://github.com/mitsuhiko/pluginbase', py_modules=['pluginbase'], description='A support library for building plugins sytems in Python.', zip_safe=False, classifiers=[ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: PyPy', 'Environment :: Plugins', 'Intended Audience :: Developers', ] ) pluginbase-0.5/Makefile0000664000175000017500000000032312760017765016326 0ustar steinersteiner00000000000000test: @cd tests; PYTHONPATH=.. py.test --tb=native upload-docs: $(MAKE) -C docs dirhtml rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/pluginbase.pocoo.org/static/ .PHONY: test upload-docs pluginbase-0.5/setup.cfg0000664000175000017500000000007313005211740016467 0ustar steinersteiner00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pluginbase-0.5/docs/0000775000175000017500000000000013005211740015576 5ustar steinersteiner00000000000000pluginbase-0.5/docs/Makefile0000664000175000017500000001073112760017765017262 0ustar steinersteiner00000000000000# 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) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 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 " 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/Classy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Classy.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/Classy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Classy" @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 all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." latexpdf: latex $(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." 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." pluginbase-0.5/docs/conf.py0000664000175000017500000001553212760017765017125 0ustar steinersteiner00000000000000# -*- coding: utf-8 -*- # # pluginbase documentation build configuration file, created by # sphinx-quickstart on Mon Apr 26 19:53:01 2010. # # 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.append(os.path.abspath('_themes')) sys.path.append(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', 'sphinx.ext.intersphinx'] # 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'pluginbase' copyright = u'2014, Armin Ronacher' # 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.0' # The full version, including alpha/beta/rc tags. release = '1.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 # 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. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'flask_small' # 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 = { 'github_fork': 'mitsuhiko/pluginbase' } # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'pluginbase' # 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'pluginbasedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pluginbase.tex', u'pluginbase documentation', u'Armin Ronacher', '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 # Additional stuff for the LaTeX preamble. #latex_preamble = '' # 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', 'pluginbase', u'pluginbase documentation', [u'Armin Ronacher'], 1) ] intersphinx_mapping = { 'http://docs.python.org/dev': None } pluginbase-0.5/docs/index.rst0000664000175000017500000001362613005207110017443 0ustar steinersteiner00000000000000PluginBase ========== .. module:: pluginbase Ever tried creating a plugin system for a Python application and you discovered fighting against the import system? Me too. This is where PluginBase comes in. PluginBase is a module for Python which extends the import system for the most common form of plugin usage which is providing a consistent import experience for plugins from a variety of sources. Essentially it allows you to build very flexible plugin based applications which pull in plugins from bundled sources as well as application specific ones without bypassing the Python import system. How does it work? It's super simple: Step 1: Create a "plugin base" object. It defines a pseudo package under which all your plugins will reside. For instance it could be ``yourapplication.plugins``:: from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') Step 2: Now that you have a plugin base, you can define a plugin source which is the list of all sources which provide plugins:: plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) Step 3: To import a plugin all you need to do is to use the regular import system. The only change is that you need to import the plugin source through the ``with`` statement:: with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() Alternatively you can also import plugins programmatically instead of using the import statement:: my_plugin = plugin_source.load_plugin('my_plugin') For a more complex example see the one from the git repo: `pluginbase-example `__. Installation ------------ You can get the library directly from PyPI:: pip install pluginbase FAQ --- Q: Why is there a plugin base and a plugin source class? This decision was taken so that multiple applications can co-exist together. For instance imagine you have an application that implements a wiki but you want multiple instances of that wiki to exist in the same Python interpreter. The plugin sources split out the load paths of the different applications. Each instance of the wiki would have its own plugin source and they can work independently of each other. Q: Do plugins pollute ``sys.modules``? While a plugin source is alive the plugins do indeed reside in ``sys.modules``. This decision was made consciously so that as little as possible of the Python library ecosystem breaks. However when the plugin source gets garbage collected all loaded plugins will also get garbage collected. Q: How does PluginBase deal with different versions of the same plugin? Each plugin source works independently of each other. The way this works is by internally translating the module name. By default that module name is a random number but it can also be forced to a hash of a specific value to make it stable across restarts which allows pickle and similar libraries to work. This internal module renaming means that ``yourapplication.module.foo`` will internally be called ``pluginbase._internalspace._sp7...be4`` for instance. The same plugin loaded from another plugin source will have a different internal name. Q: What happens if a plugin wants to import other modules? All fine. Plugins can import from itself as well as other plugins that can be located. Q: Does PluginBase support pickle? Yes, pickle works fine for plugins but it does require defining a stable identifier when creating a plugin source. This could for instance be a file system path:: plugin_source = base.make_plugin_source( searchpath=[app.plugin_path], identifier=app.config_filename) Q: What happens if I import from the plugin module without the plugin source activated through the ``with`` statement? The import will fail with a descriptive error message explaining that a plugin source needs to be activated. Q: Can I automatically discover all modules that are available? Yes you can. Just use the :meth:`PluginSource.list_plugins` method which returns a list of all plugins that a source can import. Q: Why would I use this over setuptools based plugins? PluginBase and setuptools based plugins solve very different problems and are incompatible on an architectural point of view. PluginBase does not solve plugin distribution through PyPI but allows plugins to be virtualized from each other. Setuptools on the other hand is based on PyPI based distribution but piggybacks on top of the regular import system. There are advantages and disadvantages to both of them. Setuptools based plugins are very useful to extend libraries from other libraries. For instance the Jinja2 template engine hooks into the Babel library for internationalization through setuptools. On the other hand applications distributed to users can benefit from a PluginBase based system which allows them to take control over how plugins are distributed and full separation from each other. API --- High Level `````````` .. autoclass:: PluginBase :members: .. autoclass:: PluginSource :members: .. autofunction:: get_plugin_source Import Hook Control ``````````````````` .. autofunction:: pluginbase.import_hook.enable .. autofunction:: pluginbase.import_hook.disable .. data:: pluginbase.import_hook.enabled Indicates if the import hook is currently active or not. Internals ````````` .. autodata:: pluginbase._internalspace This module is where pluginbase keeps track of all loaded plugins. Generally one can completely ignore the existence of it, but in some situations it might be useful to discover currently loaded modules through this when debugging. pluginbase-0.5/docs/_static/0000775000175000017500000000000013005211740017224 5ustar steinersteiner00000000000000pluginbase-0.5/docs/_static/itsdangerous.png0000664000175000017500000004503312760017765022470 0ustar steinersteiner00000000000000PNG  IHDRȀsBIT|d pHYs  mtEXtSoftwarewww.inkscape.org< IDATxu]Շw)+Zݽ8 -^ܡ@A(E''$d}eΜ3g$}##Y{o/f>sw' IO]ֻ  uͬed:62AALO/kffO~ol ifY.kAD`Xk30p?;AAt&=OlN'$ٶv!fG4 N2[90*=3Y 6K@VW9lVtkoP3AAIxX }_6vkml]V?f>ѝ p;a(  X̶kwX   Jj`{`< f@.1k~l AA pGYQDc@/4pG3۬v ;a wA l_`2QU`07& w۠B} ~YC{M0%rM&GǺO\wkz[f#9CWAA*:6`G`f6.֠afY9IBб3 κ6;e/@ HlF3"3or`I ppmaY%vrF/.U|W ߞh =_}00#zxc`O܎*=`5`S3;Ef=}sߟ^A Gi s# 91>~^>XhfYMQdڛދx =p3AA}镟oR0+o‚yq`U`gZ3+wA f-zRXh'ŀ ?dQllr`g`Z`xt5f6h4f6zyË́G_{]!hNN^_gj<"haOz3ݟlSȿ Atb^8f9-Y}_a;%nkY-f6+p$Du̾B-JU I~#〟Ț+& ||G `u{kmn93ݿgirt~|zKz ۙ X]-gi  Ϲ+e 'rf.cV3YAꎶc~F`g>9 ,?p[fעlxzg>Á*f;:k|\^rlPdqMSE+'"nrc)tπ.瀵gTxl7`Ywc9;61bfrE{(UJNFo4df?"ߖa-HNnVKZ<,/ְcf EuG18T:㑸յnlÀݽh:lcLZDB/P`̬w_ m/~;|7iy#&}Ebk9Moyۢ33W/{krۜs !0=ieXw\uKsn-ah}t,l ~Dsݽ*맙Nk1mro}wK ~_<zςjJ|~`;d^)Q#QD6,NEoτ n8܅2W2anMw餿ޅ鼭:4]e4 SZ秴%L?5u>uthc"7:als,0cSv^4hzea[G9.F?ˠ_e(r̍vs HL/ lTks{2B*Ǜu>3}ÁπʹҹY7 Ŷo o_^5;K^V-e߁ݝr7t,RKZHA=#_YQ'IW3زum2כ[ۙBjo2;8Ӈ+F9s7r |v.rCqdUX~v@p2evvEPY[jT8!}l?e7 =by[$p^>O˃ N*Z Y}}X.qާz BeY^p"~O@c3#1"ꅜ7Do dJ%= X*ep Ա?Eqw8" EQl>Q+~,H~/?-梭ɞU<0A?_Pm7oj锻?!9weĭ&ZEtQ7Je73;̖@2uQմE֭OqJ)vjW{ۻ !̡WƫIڲot`g@ ߠSJ[!0+rF7dPn?&xv]˯zy+HTQPtuusu?ϣYޢZ5 (AB:o=V'z}VtSN/07"4f|}_+ߺ'Ne!ݠAp? OzCsY>s@2/795+X /Sw WzǹKr7wt5~B7lzf6gQ#i=*]6"x)rZ@cZY\ſyR UN$^sVtDCBzf̦ANVCuHlQB;aM 3ukS)npe:X6@H2-.5q=D^W27 9:hpCҿ7~{蚉x;ȴkho_9Ί\])7S!!fQn(|;jvgЭ,3A۪3ںSu_nTȉ8-v LOdxfg 'J|4UVFE Jk%O3+V f$3EfW^4f0,Kټef+✔7LEUIڹ5moǚaTGvg~+v+ BJuH+/̷NB LӔdu*7ɭ\.)aOdǺ'R W$[Ud}dIᆉwz3?T*լtME-UE;̍ -殕6Ywf_>|9?sWH3:/A3{&OȞxsPiZ CJl ")??VH~r (>xݟ*@r̺67%;w3h"YnTE%a"Ӻ/rYӺ4u^ۺJVc" 0)3{1lgff][ch>OS 0+Vz ;E;&ٱf6{61lX笧ױRut$x!Kى̋ЛfG$v3Pm聿AVOfbme*tmBwTz`lCcӟ{'Tl"oM+Xw {za+7mCͪ"=B߲&Gr*qR,Vq.!9iol3zS˧ɾ/YXkO9}#M~$Za*U(ROKyV,Flgb| do''Q~=wo}h@HyA !JHTz`Pf6& ELt#zwF[fWZ#UpFOC]@H]vH_DR%loMۙ!uH?\@pwORS:@wχ~d@>Kaf7ERZA+*"Nܘ^ ;Z`#]wS. +\jh2HCgBVt/`rgQqovJ49n \B cfk{"'jC.:<3wP f` 7 AnBI/=UJ?DzaKPv7Us5 [mjtZ-9+){Gٗ6Y T#CD_aYd{Q-U\"Vj g.՚bV쩐]=@ȺwL5/Cȕ"y. Ѓ=Tfd}cdK?cӸkw"/ ?K~̳iy}ʒy1(OX%2-NGŞopVH&-J{ezt/(F\i&ż'Ծ&R|U/p>F>4u;3;e"mmVRׄx\~;wj1Kw= }0VX,lf4Ӷ?sNC07AB2,2.Jgfk1bmbo3=ݽ /D3^ |; F plUT>6r(v&mYcк3*T:zZ޺:bf+LVB7uwgf7:,H$ T(FZW؞>k!Ш6qNWX+n 3;O07}P%v f0z.<6zxqH@2 Ѻ]$?CXn')LgC'qf6Sg|yS|zY?@e(OB1k#oݟ?¢49,PMvZ~(wg>CUs ',s!5WjZv&^ j!B5ßZ`p;3r|D>儉t3[Jm(Q\SB=( t (9h8bl7&?Bo0Uu؏oʂPoL2ڦb,~Nx6eVe+^$@&!UloZI$lM[ za( =V49+pT]-jt@ȟrU +{1rجfV&%bj$ٜ+\^1:[#i%1 w~ONn 2<4IJ ZV}̷rGڟ*iMr-s *֘ϛW gW%i+GPYYdm<17y2 5J3 1}pP,A1+.m?[-Y'aaSj rŔ~ɪX i n4=^BY̿:(UzM6Tï4R4r0Wr4҃vzV-Pby]ASr|OPjhX/CkN"aKfv}gڹn3DJy!\mhZ1+i%/;Sy|2Ϣ%1R5əxC䕑\)#8_f5XUN &;{S}M`?8=PDjx4zC>%ڜ:7=ElkX n IDAT'm? KIak]xk}n.3g_ʹ18_aE W[}=6ͭaGo$dۚ6FÚhc43,SU)h3Ԩ͵iWGlsbm4LOzTΪ(qQoϠiJ ͬ{/Psl):XuLV}90SuEJ}Qgi@?[Pp$6Bq;=wޕۗ+u:o&g,zPUDn _ۼ cz̮mאIc?=5Y%@ouǸ:Z3]3?Cg9Z'AT9Bz:4C`WM-p+ :k6YȷUw6YPG<8rKR:W9,n_=gG8#3:/|cPhꏩ}h|5 QbCbiwFtp 0ĕpT;==cOd\ju$FgQZ K=ኺ;f Ikh80}F ˧a.Ăl+Z_UaT;Tt~[f*!sz>zKy2k E 0#Rw I\Lc{ ڧhqN^X~1޹MW(vОJ2 2@5GF/Y);[VwiO>`ǡku8ƹx+lW|3#Uvz͞+7~k9r.:c4]w}R=Ѝ{rwk_Aڝ M ܄?ߡ7\QId=~ -3 e]ݳ-ms9{2-p :דg>?^aZ$l@B{^6gd;vrg&7"zᕍW~ȶhwALW%Ahv^oinCl,R #ճ(z~lYB$̌szBuާE? sǵ v&fկg k8y#ޠaf\YTFV `W"$3LHMy+K|Twȿe$Ɔ#kYfCi=p_=;Ȋ~ +9.DH$<ǻ62- Lz)AF 3;)Tyj 42͏?HU: Dӄ9v:GM&G!X+dʺ8puPGSF#Aݍn!(ȓx*Z~HH F0D(vҔr3 @pP#uQ7y. In/ ?+VtZPz+PmիO5l> B (S r_XHx 7if[5t hDpT9a4$VF"a< !08Enk٦ ݁  h&*aȒ0WPcA$,Gi*umAA0 W>'#T^dIF ̀-]$J 7D…f^#! N /"Ȓʁp-"(!Ҫ(eqWulFCAt%@_DhTiTtv47d=Dª(OB}X$@YW!x)(V /"awpYޘ3tcl>3{]ܥ0 w:@΅,$,|@WY6.ᆠꆌ¾L2ٔfgTj2fs$$ l _Dj EfE*`+ow.Y "aB0 }O$\4̦ wYLjGטr-h^&cPTH$n!kKאHb"|hqI_kc3A-} IJ  `$HShUZ|nTIvK ad`4Y5`^{fe -r:Y fDF CQ"Hu$2%!2~fV҉ܝѧ_vK7f60w?ke ;p=0=/\$>A~ %}JcfoTINi(f@5N :aӿtu_IV nh&{ "'c\u!fvbvAG, nTrg}K,wt:E=Pe7QR+Q4VE#Nݓ'EBDwYnz#KyH4,<-͏$ffGhw4{I螘 fvdf=3;u&3]B3{n7l*Q+fv]iff62C1[3if7قzI3CZ7܊fvofa*3;̞F03fl `}:3̆ٹ_Z6/qi1uY~t6lgo32l[31mffkf]jfTӠ[IG{V40]c3۷͞ 3kf1ׁ1f6>6ͬ%t1wPP?4BNK ³IezSE| oC{"YGb"g0]*(`lfE֝ۯv_04m̲z%Z r}2YV`\CPJ , OFVp"'GQ´;R;䖝e,l"5w<~H*0eZo2gg?#~ӹ?U㛖?+-y:qpm Hm2' Gƍi]ێO.@3}H;,D"a( 7qF}>`%Dd])Z8ЮM_ =M_X 7`ZV؏-27GU҃j2>3iri=͑2ӼJŴ:X?^t3!HTH{ }CH(\]-zn?sn} L7Tm|7g_UZ˝P`- AE B"뮎yYf4y=+~h }=tʲx7]An~~])\mnt_sG-7 i̍u yZg$02힝CznheCJkhZ)p`}BI}XGNGEg3_nkfYow/vs \Ee3i|?k*mGPE/H+uH6̱mhq}Gl-} Eȉx9h2-|R=N@VS,R65IFKo٩627>&_FrPm}ȴXX̞+!?N0E ,efo&(dxqwO ~N'W5tb nffu'mg'O@!)E+$G{Rod;k1ʰ7FcT A} I$DBu2p",ތ^Go'6cmf6sjw4W>*yPߌ砇VMYtwFS̎ȵjf_13lNڝ ;w? - NgCI dKk'L[ >,SV ܽQ-RLVlb2_[Xݯw. 2y/M޳DHa ȋJe3F8}ӐOԒ;VU wF!^hB +lavCn G"!K p ;gH3>2=ef/Y}S̬̀E;`p}S0Gfhy*v6EhO=YhW(AFY0݋v R,ҹcRd90w{f}SEͫ3-os[PHac6+;?Ǻ.hXd E@WsE5B͎zKJ 6Z swC@+G񳶳iL#,N"67-$N-LXZ!WHǼ0dLA(aهiv-9g#A-=ӧWZ wS%F͂5ݱhXw2 wlk$OA# ЛȁgM#0# :+N*13~%!-8#cgt %BlݍnBM\)}֟|F/{$+!("ovf炠@>=8<* BzˠbpY̶Ao(lŌH8-DBt)7R83{=̞F ˺S] #PGlAx8p *$%} .BRWCaqSS'GOBt )t(mN[60ċT L:7=]| C_=}Mviz ӈ!:'}J8st6%?YYB M7S~E}l ݐOBT J/g!]PnQzQRfwZCw,d= pFPR[l]lf 9 yNkq-fD굽 I u< Q3QճP(ЃHVB> Y$NJfI^6-$& U&\mAON.Aowb.["h`WvgpqǙRI$l"! !kG(Lw<|a;AA7 B'"N!Ks(Tq(rJ| _*1[H2_ifGC-Uvo^7虦) pWFAMɸ9(' x0j3L7!.7" `f6 UI M},}U;At+"0ߣo{ȁyȯkp 7\U)-F!ƛٔ(Hw̾l7Y6[*4\ALĄpѐÖ(({O1R8E7"6N`v (>C5(qS ~f&M I t1f6Jt p*+xlYL_Y6r f"df # 3.rhgs Q2 4rf6J}fQ{ACXw \ Cy $VN5Ɂ8̊JE2Nb$0Q%`$EC* qA0&6@(9"Waq G7,( pnf롰ϷK#c3!m:f$d I 4 ~'r<Op,Yq0Y ~Ű:po I 4)1(EhT^hu$"By."441-/G8EQ] &-‚d =C;ρ CP QmgIl<|0w=?D96ȉŀCALI15Q(|+$ CQ cbˎ. IDATT2Rpiw4NH/ͅJGoF!EQ~|2;m #f,n,OQhW(( hazTqw Eɕ6 A&1MHiKsf0_M IX D`<0-0m  6m}H+ hN‚D$À3[| ^z y:f, If6u>+l]T B ` BafW#rB|ؚ"?Ƹ? q\0Qp~}eig$Dsj {IH#U݊,-9XYEf6uqgEKM?hxw= l}jHdi8 3~ ̉ʊw<'.XFǡ0S, 讄 0;'`ifPP;P(4(Rgȧ`lb#]7(ZfvT$qU_\8{rAjǁPDOTˀl 'E  t1f OY'p%pWvE.F_Er1?#*OT4jA4aAzV+ŁMofBJ[ĵji;_aQeY6s`9`+$  4f6'mzYoyn{ p7kx 3^r}lc }38MAI&zkq(sM~mx L8)auv`w G3[ xzX8]sX'8:M0< hϣ"Q%$mT[Qρ P6ȵQeAЍ,P)SlJ?K~@ΕAA7CO`G٪`JD/3g40 U|4 9F At3!(AQ3 ǡaatZn%bw,JUNͭńEAФ@%EV eM\?1_"B?|`Z$ f[!(y]AAՄ@JJ< e^<Ux| fkvA7 :>AI 3(3~ `fS] "!( w19%|gfmAi(Ym[ b|ERAwvl;Tmr.w 'BP6f,ugzuȪ-rTx8A5>A%, @E eFdK{] v‚TD` 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. 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 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "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. goto end ) if "%1" == "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\Classy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Classy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "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. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pluginbase-0.5/docs/_themes/0000775000175000017500000000000013005211740017222 5ustar steinersteiner00000000000000pluginbase-0.5/docs/_themes/flask_theme_support.py0000664000175000017500000001141312760017765023674 0ustar steinersteiner00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } pluginbase-0.5/docs/_themes/README0000664000175000017500000000210512760017765020122 0ustar steinersteiner00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge pluginbase-0.5/docs/_themes/LICENSE0000664000175000017500000000337512760017765020261 0ustar steinersteiner00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pluginbase-0.5/docs/_themes/.gitignore0000664000175000017500000000002612760017765021232 0ustar steinersteiner00000000000000*.pyc *.pyo .DS_Store pluginbase-0.5/docs/_themes/flask_small/0000775000175000017500000000000013005211740021512 5ustar steinersteiner00000000000000pluginbase-0.5/docs/_themes/flask_small/theme.conf0000664000175000017500000000026512760017765023510 0ustar steinersteiner00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 160px github_fork = pluginbase-0.5/docs/_themes/flask_small/static/0000775000175000017500000000000013005211740023001 5ustar steinersteiner00000000000000pluginbase-0.5/docs/_themes/flask_small/static/flasky.css_t0000664000175000017500000001076712760017765025364 0ustar steinersteiner00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); @import url(http://fonts.googleapis.com/css?family=Karla:400); {% set font_family = "'Karla', sans-serif" %} /* -- page layout ----------------------------------------------------------- */ body { font-family: {{ font_family }}; font-weight: normal; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #215974; text-decoration: underline; } a:hover { color: #888; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: {{ font_family }}; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; color: black; } div.body h1 { font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li, div.body blockquote { line-height: 1.3em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } pluginbase-0.5/docs/_themes/flask_small/layout.html0000664000175000017500000000136712760017765023746 0ustar steinersteiner00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} pluginbase-0.5/tests/0000775000175000017500000000000013005211740016010 5ustar steinersteiner00000000000000pluginbase-0.5/tests/conftest.py0000664000175000017500000000115512760017765020233 0ustar steinersteiner00000000000000import gc import pytest from pluginbase import PluginBase @pytest.fixture(scope='function') def base(): return PluginBase(package='dummy.plugins') @pytest.fixture(scope='function') def dummy_internal_name(): return 'pluginbase._internalspace._sp7bb7d8da1d24ae5a5205609c951b8be4' @pytest.fixture(scope='function') def source(base): return base.make_plugin_source(searchpath=['./plugins'], identifier='demo') @pytest.yield_fixture(scope='function', autouse=True) def run_garbage_collection(): gc.collect() try: yield finally: gc.collect() pluginbase-0.5/tests/plugins/0000775000175000017500000000000013005211740017471 5ustar steinersteiner00000000000000pluginbase-0.5/tests/plugins/hello.py0000664000175000017500000000032012760017765021163 0ustar steinersteiner00000000000000def import_self(): from dummy.plugins import hello return hello def get_plugin_source(): from pluginbase import get_plugin_source return get_plugin_source() def demo_func(): return 42 pluginbase-0.5/tests/plugins/withresources/0000775000175000017500000000000013005211740022377 5ustar steinersteiner00000000000000pluginbase-0.5/tests/plugins/withresources/__init__.py0000664000175000017500000000002412760017765024526 0ustar steinersteiner00000000000000def foo(): pass pluginbase-0.5/tests/plugins/withresources/hello.txt0000664000175000017500000000002112760017765024256 0ustar steinersteiner00000000000000I am a textfile. pluginbase-0.5/tests/plugins/advanced.py0000664000175000017500000000030012760017765021623 0ustar steinersteiner00000000000000from pluginbase import get_plugin_source def get_app(): rv = get_plugin_source(stacklevel=1) if rv is not None: return rv.app def get_app_name(): return get_app().name pluginbase-0.5/tests/plugins/hello2.py0000664000175000017500000000003612760017765021251 0ustar steinersteiner00000000000000def awesome_stuff(): pass pluginbase-0.5/tests/test_advanced.py0000664000175000017500000000103112760017765021203 0ustar steinersteiner00000000000000import pytest def test_custom_state(base): class App(object): name = 'foobar' source = base.make_plugin_source(searchpath=['./plugins']) source.app = App() plg = source.load_plugin('advanced') assert plg.get_app_name() == 'foobar' def test_plugin_resources(source): with source.open_resource('withresources', 'hello.txt') as f: contents = f.read() assert contents == b'I am a textfile.\n' with pytest.raises(IOError): source.open_resource('withresources', 'missingfile.txt') pluginbase-0.5/tests/shutdown.py0000664000175000017500000000042312760017765020256 0ustar steinersteiner00000000000000from pluginbase import PluginBase base = PluginBase(package='dummy.modules') plugin_source = base.make_plugin_source( searchpath=['./plugins']) # This dangles around. This will be collected when the interpreter # shuts down. hello = plugin_source.load_plugin('hello') pluginbase-0.5/tests/test_basics.py0000664000175000017500000000551112760017765020711 0ustar steinersteiner00000000000000import gc import sys from pluginbase import get_plugin_source def test_basic_plugin(source, dummy_internal_name): # When the source is active the import gives us a module with source: from dummy.plugins import hello # Which because of a stable identifier has a predictable name. assert hello.__name__ == dummy_internal_name + '.hello' # And can continue to import from itself. assert hello.import_self() is hello # On the other hand without a source will fall flat on the floor. try: from dummy.plugins import hello except RuntimeError: pass else: assert False, 'Expected a runtime error but managed to ' \ 'import hello (%s)' % hello def test_fetching_plugin_source(source): # Finding the plugin source outside of a plugin and without a with # block of a plugin source returns None. assert get_plugin_source() is None # Inside a source block we can find the source through mere calling. with source: assert get_plugin_source() is source # A module can always find its own source as well (the hello module # calls get_plugin_source() itself). with source: from dummy.plugins import hello assert hello.get_plugin_source() is source # Last but not least the plugin source can be found by module names # (in a plugin source block by the import name and in any case by # the internal name) with source: assert get_plugin_source('dummy.plugins.hello') is source assert get_plugin_source(hello.__name__) is source # As well as by module object. assert get_plugin_source(hello) is source def test_cleanup(base): new_source = base.make_plugin_source(searchpath=['./plugins']) mod_name = new_source.mod.__name__ assert sys.modules.get(mod_name) is new_source.mod with new_source: from dummy.plugins import hello new_source = None gc.collect() assert sys.modules.get(mod_name) is None assert hello.import_self is None def test_persist(base): new_source = base.make_plugin_source(searchpath=['./plugins'], persist=True) mod_name = new_source.mod.__name__ assert sys.modules.get(mod_name) is new_source.mod with new_source: from dummy.plugins import hello new_source = None assert sys.modules.get(mod_name) is not None assert hello.import_self is not None sys.modules[mod_name].__pluginbase_state__.source.cleanup() assert sys.modules.get(mod_name) is None assert hello.import_self is None def test_list_plugins(source): plugins = source.list_plugins() hello_plugins = [x for x in plugins if x.startswith('hello')] assert hello_plugins == ['hello', 'hello2'] def test_load_plugin(source): hello = source.load_plugin('hello') assert hello.demo_func() == 42 pluginbase-0.5/tests/test_shutdown.py0000664000175000017500000000063512760017765021322 0ustar steinersteiner00000000000000import os import sys import subprocess def test_clean_shutdown(): env = dict(os.environ) env['PYTHONPATH'] = '..:.' c = subprocess.Popen([sys.executable, '-c', 'import shutdown'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) stdout, stderr = c.communicate() assert stdout == b'' assert stderr == b'' pluginbase-0.5/tests/dummy.py0000664000175000017500000000007712760017765017543 0ustar steinersteiner00000000000000# make sure there is a module we can base our modules against. pluginbase-0.5/pluginbase.py0000664000175000017500000003511713005211566017366 0ustar steinersteiner00000000000000# -*- coding: utf-8 -*- """ pluginbase ~~~~~~~~~~ Pluginbase is a module for Python that provides a system for building plugin based applications. :copyright: (c) Copyright 2014 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import os import sys import uuid import errno import pkgutil import hashlib import threading from types import ModuleType from weakref import ref as weakref PY2 = sys.version_info[0] == 2 if PY2: text_type = unicode string_types = (unicode, str) from cStringIO import StringIO as NativeBytesIO else: text_type = str string_types = (str,) from io import BytesIO as NativeBytesIO __version__ = '0.5' _local = threading.local() _internalspace = ModuleType(__name__ + '._internalspace') _internalspace.__path__ = [] sys.modules[_internalspace.__name__] = _internalspace def get_plugin_source(module=None, stacklevel=None): """Returns the :class:`PluginSource` for the current module or the given module. The module can be provided by name (in which case an import will be attempted) or as a module object. If no plugin source can be discovered, the return value from this method is `None`. This function can be very useful if additional data has been attached to the plugin source. For instance this could allow plugins to get access to a back reference to the application that created them. :param module: optionally the module to locate the plugin source of. :param stacklevel: defines how many levels up the module should search for before it discovers the plugin frame. The default is 0. This can be useful for writing wrappers around this function. """ if module is None: frm = sys._getframe((stacklevel or 0) + 1) name = frm.f_globals['__name__'] glob = frm.f_globals elif isinstance(module, string_types): frm = sys._getframe(1) name = module glob = __import__(module, frm.f_globals, frm.f_locals, ['__dict__']).__dict__ else: name = module.__name__ glob = module.__dict__ return _discover_space(name, glob) def _discover_space(name, globals): try: return _local.space_stack[-1] except (AttributeError, IndexError): pass if '__pluginbase_state__' in globals: return globals['__pluginbase_state__'].source mod_name = globals.get('__name__') if mod_name is not None and \ mod_name.startswith(_internalspace.__name__ + '.'): end = mod_name.find('.', len(_internalspace.__name__) + 1) space = sys.modules.get(mod_name[:end]) if space is not None: return space.__pluginbase_state__.source def _shutdown_module(mod): members = list(mod.__dict__.items()) for key, value in members: if key[:1] != '_': setattr(mod, key, None) for key, value in members: setattr(mod, key, None) def _to_bytes(s): if isinstance(s, text_type): return s.encode('utf-8') return s class _IntentionallyEmptyModule(ModuleType): def __getattr__(self, name): try: return ModuleType.__getattr__(self, name) except AttributeError: if name[:2] == '__': raise raise RuntimeError( 'Attempted to import from a plugin base module (%s) without ' 'having a plugin source activated. To solve this error ' 'you have to move the import into a "with" block of the ' 'associated plugin source.' % self.__name__) class _PluginSourceModule(ModuleType): def __init__(self, source): modname = '%s.%s' % (_internalspace.__name__, source.spaceid) ModuleType.__init__(self, modname) self.__pluginbase_state__ = PluginBaseState(source) @property def __path__(self): try: ps = self.__pluginbase_state__.source except AttributeError: return [] return ps.searchpath + ps.base.searchpath def _setup_base_package(module_name): try: mod = __import__(module_name, None, None, ['__name__']) except ImportError: mod = None if '.' in module_name: parent_mod = __import__(module_name.rsplit('.', 1)[0], None, None, ['__name__']) else: parent_mod = None if mod is None: mod = _IntentionallyEmptyModule(module_name) if parent_mod is not None: setattr(parent_mod, module_name.rsplit('.', 1)[-1], mod) sys.modules[module_name] = mod class PluginBase(object): """The plugin base acts as a control object around a dummy Python package that acts as a container for plugins. Usually each application creates exactly one base object for all plugins. :param package: the name of the package that acts as the plugin base. Usually this module does not exist. Unless you know what you are doing you should not create this module on the file system. :param searchpath: optionally a shared search path for modules that will be used by all plugin sources registered. """ def __init__(self, package, searchpath=None): #: the name of the dummy package. self.package = package if searchpath is None: searchpath = [] #: the default search path shared by all plugins as list. self.searchpath = searchpath _setup_base_package(package) def make_plugin_source(self, *args, **kwargs): """Creats a plugin source for this plugin base and returns it. All parameters are forwarded to :class:`PluginSource`. """ return PluginSource(self, *args, **kwargs) class PluginSource(object): """The plugin source is what ultimately decides where plugins are loaded from. Plugin bases can have multiple plugin sources which act as isolation layer. While this is not a security system it generally is not possible for plugins from different sources to accidentally cross talk. Once a plugin source has been created it can be used in a ``with`` statement to change the behavior of the ``import`` statement in the block to define which source to load the plugins from:: plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) with plugin_source: from myapplication.plugins import my_plugin :param base: the base this plugin source belongs to. :param identifier: optionally a stable identifier. If it's not defined a random identifier is picked. It's useful to set this to a stable value to have consistent tracebacks between restarts and to support pickle. :param searchpath: a list of paths where plugins are looked for. :param persist: optionally this can be set to `True` and the plugins will not be cleaned up when the plugin source gets garbage collected. """ # Set these here to false by default so that a completely failing # constructor does not fuck up the destructor. persist = False mod = None def __init__(self, base, identifier=None, searchpath=None, persist=False): #: indicates if this plugin source persists or not. self.persist = persist if identifier is None: identifier = str(uuid.uuid4()) #: the identifier for this source. self.identifier = identifier #: A reference to the plugin base that created this source. self.base = base #: a list of paths where plugins are searched in. self.searchpath = searchpath #: The internal module name of the plugin source as it appears #: in the :mod:`pluginsource._internalspace`. self.spaceid = '_sp' + hashlib.md5( _to_bytes(self.base.package) + b'|' + _to_bytes(identifier), ).hexdigest() #: a reference to the module on the internal #: :mod:`pluginsource._internalspace`. self.mod = _PluginSourceModule(self) if hasattr(_internalspace, self.spaceid): raise RuntimeError('This plugin source already exists.') sys.modules[self.mod.__name__] = self.mod setattr(_internalspace, self.spaceid, self.mod) def __del__(self): if not self.persist: self.cleanup() def list_plugins(self): """Returns a sorted list of all plugins that are available in this plugin source. This can be useful to automatically discover plugins that are available and is usually used together with :meth:`load_plugin`. """ rv = [] for _, modname, ispkg in pkgutil.iter_modules(self.mod.__path__): rv.append(modname) return sorted(rv) def load_plugin(self, name): """This automatically loads a plugin by the given name from the current source and returns the module. This is a convenient alternative to the import statement and saves you from invoking ``__import__`` or a similar function yourself. :param name: the name of the plugin to load. """ if '.' in name: raise ImportError('Plugin names cannot contain dots.') with self: return __import__(self.base.package + '.' + name, globals(), {}, ['__name__']) def open_resource(self, plugin, filename): """This function locates a resource inside the plugin and returns a byte stream to the contents of it. If the resource cannot be loaded an :exc:`IOError` will be raised. Only plugins that are real Python packages can contain resources. Plain old Python modules do not allow this for obvious reasons. .. versionadded:: 0.3 :param plugin: the name of the plugin to open the resource of. :param filename: the name of the file within the plugin to open. """ mod = self.load_plugin(plugin) fn = getattr(mod, '__file__', None) if fn is not None: if fn.endswith(('.pyc', '.pyo')): fn = fn[:-1] if os.path.isfile(fn): return open(os.path.join(os.path.dirname(fn), filename), 'rb') buf = pkgutil.get_data(self.mod.__name__ + '.' + plugin, filename) if buf is None: raise IOError(errno.ENOENT, 'Could not find resource') return NativeBytesIO(buf) def cleanup(self): """Cleans up all loaded plugins manually. This is necessary to call only if :attr:`persist` is enabled. Otherwise this happens automatically when the source gets garbage collected. """ self.__cleanup() def __cleanup(self, _sys=sys, _shutdown_module=_shutdown_module): # The default parameters are necessary because this can be fired # from the destructor and so late when the interpreter shuts down # that these functions and modules might be gone. if self.mod is None or self.mod.__name__ is None: return modname = self.mod.__name__ self.mod.__pluginbase_state__ = None self.mod = None try: delattr(_internalspace, self.spaceid) except AttributeError: pass prefix = modname + '.' # avoid the bug described in issue #6 if modname in _sys.modules: del _sys.modules[modname] for key, value in list(_sys.modules.items()): if not key.startswith(prefix): continue mod = _sys.modules.pop(key, None) if mod is None: continue _shutdown_module(mod) def __assert_not_cleaned_up(self): if self.mod is None: raise RuntimeError('The plugin source was already cleaned up.') def __enter__(self): self.__assert_not_cleaned_up() _local.__dict__.setdefault('space_stack', []).append(self) return self def __exit__(self, exc_type, exc_value, tb): try: _local.space_stack.pop() except (AttributeError, IndexError): pass def _rewrite_module_path(self, modname): self.__assert_not_cleaned_up() if modname == self.base.package: return self.mod.__name__ elif modname.startswith(self.base.package + '.'): pieces = modname.split('.') return self.mod.__name__ + '.' + '.'.join( pieces[self.base.package.count('.') + 1:]) class PluginBaseState(object): __slots__ = ('_source',) def __init__(self, source): if source.persist: self._source = lambda: source else: self._source = weakref(source) @property def source(self): rv = self._source() if rv is None: raise AttributeError('Plugin source went away') return rv class _ImportHook(ModuleType): def __init__(self, name, system_import): ModuleType.__init__(self, name) self._system_import = system_import self.enabled = True def enable(self): """Enables the import hook which drives the plugin base system. This is the default. """ self.enabled = True def disable(self): """Disables the import hook and restores the default import system behavior. This effectively breaks pluginbase but can be useful for testing purposes. """ self.enabled = False def plugin_import(self, name, globals=None, locals=None, fromlist=None, level=None): if level is None: # set the level to the default value specific to this python version level = -1 if PY2 else 0 import_name = name if self.enabled: ref_globals = globals if ref_globals is None: ref_globals = sys._getframe(1).f_globals space = _discover_space(name, ref_globals) if space is not None: actual_name = space._rewrite_module_path(name) if actual_name is not None: import_name = actual_name return self._system_import(import_name, globals, locals, fromlist, level) try: import __builtin__ as builtins except ImportError: import builtins import_hook = _ImportHook(__name__ + '.import_hook', builtins.__import__) builtins.__import__ = import_hook.plugin_import sys.modules[import_hook.__name__] = import_hook del builtins pluginbase-0.5/pluginbase.egg-info/0000775000175000017500000000000013005211740020471 5ustar steinersteiner00000000000000pluginbase-0.5/pluginbase.egg-info/top_level.txt0000664000175000017500000000001313005211737023223 0ustar steinersteiner00000000000000pluginbase pluginbase-0.5/pluginbase.egg-info/PKG-INFO0000664000175000017500000000147513005211737021603 0ustar steinersteiner00000000000000Metadata-Version: 1.1 Name: pluginbase Version: 0.5 Summary: A support library for building plugins sytems in Python. Home-page: http://github.com/mitsuhiko/pluginbase Author: Armin Ronacher Author-email: armin.ronacher@active-4.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers pluginbase-0.5/pluginbase.egg-info/SOURCES.txt0000664000175000017500000000144113005211737022363 0ustar steinersteiner00000000000000LICENSE MANIFEST.in Makefile README pluginbase.py setup.py docs/Makefile docs/conf.py docs/index.rst docs/make.bat docs/_static/itsdangerous.png docs/_themes/.gitignore docs/_themes/LICENSE docs/_themes/README docs/_themes/flask_theme_support.py docs/_themes/flask_small/layout.html docs/_themes/flask_small/theme.conf docs/_themes/flask_small/static/flasky.css_t pluginbase.egg-info/PKG-INFO pluginbase.egg-info/SOURCES.txt pluginbase.egg-info/dependency_links.txt pluginbase.egg-info/not-zip-safe pluginbase.egg-info/top_level.txt tests/conftest.py tests/dummy.py tests/shutdown.py tests/test_advanced.py tests/test_basics.py tests/test_shutdown.py tests/plugins/advanced.py tests/plugins/hello.py tests/plugins/hello2.py tests/plugins/withresources/__init__.py tests/plugins/withresources/hello.txtpluginbase-0.5/pluginbase.egg-info/dependency_links.txt0000664000175000017500000000000113005211737024545 0ustar steinersteiner00000000000000 pluginbase-0.5/pluginbase.egg-info/not-zip-safe0000664000175000017500000000000113005211710022714 0ustar steinersteiner00000000000000 pluginbase-0.5/MANIFEST.in0000664000175000017500000000032412760017765016425 0ustar steinersteiner00000000000000include Makefile LICENSE recursive-include tests * recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo recursive-exclude tests *.pyc recursive-exclude tests *.pyo prune docs/_build