New upstream snapshot.
Debian Janitor
1 year, 4 months ago
0 | Metadata-Version: 2.1 | |
1 | Name: straight.plugin | |
2 | Version: 1.4.2 | |
3 | Summary: A simple namespaced plugin facility | |
4 | Home-page: https://github.com/ironfroggy/straight.plugin | |
5 | Author: Calvin Spealman | |
6 | Author-email: ironfroggy@gmail.com | |
7 | Classifier: Programming Language :: Python :: 2 | |
8 | Classifier: Programming Language :: Python :: 3 | |
9 | Classifier: Environment :: Plugins | |
10 | License-File: LICENSE | |
11 | License-File: AUTHORS |
0 | straight.plugin (1.4.1+git20210203.1.363b0af-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream snapshot. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Fri, 30 Dec 2022 17:24:09 -0000 | |
5 | ||
0 | 6 | straight.plugin (1.4.1-4) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Debian Janitor ] |
0 | # Makefile for Sphinx documentation | |
1 | # | |
2 | ||
3 | # You can set these variables from the command line. | |
4 | SPHINXOPTS = | |
5 | SPHINXBUILD = sphinx-build | |
6 | PAPER = | |
7 | BUILDDIR = _build | |
8 | ||
9 | # Internal variables. | |
10 | PAPEROPT_a4 = -D latex_paper_size=a4 | |
11 | PAPEROPT_letter = -D latex_paper_size=letter | |
12 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | |
13 | # the i18n builder cannot share the environment and doctrees with the others | |
14 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | |
15 | ||
16 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | |
17 | ||
18 | help: | |
19 | @echo "Please use \`make <target>' where <target> is one of" | |
20 | @echo " html to make standalone HTML files" | |
21 | @echo " dirhtml to make HTML files named index.html in directories" | |
22 | @echo " singlehtml to make a single large HTML file" | |
23 | @echo " pickle to make pickle files" | |
24 | @echo " json to make JSON files" | |
25 | @echo " htmlhelp to make HTML files and a HTML help project" | |
26 | @echo " qthelp to make HTML files and a qthelp project" | |
27 | @echo " devhelp to make HTML files and a Devhelp project" | |
28 | @echo " epub to make an epub" | |
29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | |
30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" | |
31 | @echo " text to make text files" | |
32 | @echo " man to make manual pages" | |
33 | @echo " texinfo to make Texinfo files" | |
34 | @echo " info to make Texinfo files and run them through makeinfo" | |
35 | @echo " gettext to make PO message catalogs" | |
36 | @echo " changes to make an overview of all changed/added/deprecated items" | |
37 | @echo " linkcheck to check all external links for integrity" | |
38 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" | |
39 | ||
40 | clean: | |
41 | -rm -rf $(BUILDDIR)/* | |
42 | ||
43 | html: | |
44 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | |
45 | @echo | |
46 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | |
47 | ||
48 | dirhtml: | |
49 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | |
50 | @echo | |
51 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | |
52 | ||
53 | singlehtml: | |
54 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | |
55 | @echo | |
56 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | |
57 | ||
58 | pickle: | |
59 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | |
60 | @echo | |
61 | @echo "Build finished; now you can process the pickle files." | |
62 | ||
63 | json: | |
64 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | |
65 | @echo | |
66 | @echo "Build finished; now you can process the JSON files." | |
67 | ||
68 | htmlhelp: | |
69 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | |
70 | @echo | |
71 | @echo "Build finished; now you can run HTML Help Workshop with the" \ | |
72 | ".hhp project file in $(BUILDDIR)/htmlhelp." | |
73 | ||
74 | qthelp: | |
75 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | |
76 | @echo | |
77 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ | |
78 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | |
79 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/straightplugin.qhcp" | |
80 | @echo "To view the help file:" | |
81 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/straightplugin.qhc" | |
82 | ||
83 | devhelp: | |
84 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | |
85 | @echo | |
86 | @echo "Build finished." | |
87 | @echo "To view the help file:" | |
88 | @echo "# mkdir -p $$HOME/.local/share/devhelp/straightplugin" | |
89 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/straightplugin" | |
90 | @echo "# devhelp" | |
91 | ||
92 | epub: | |
93 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | |
94 | @echo | |
95 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." | |
96 | ||
97 | latex: | |
98 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
99 | @echo | |
100 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | |
101 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ | |
102 | "(use \`make latexpdf' here to do that automatically)." | |
103 | ||
104 | latexpdf: | |
105 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
106 | @echo "Running LaTeX files through pdflatex..." | |
107 | $(MAKE) -C $(BUILDDIR)/latex all-pdf | |
108 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | |
109 | ||
110 | text: | |
111 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | |
112 | @echo | |
113 | @echo "Build finished. The text files are in $(BUILDDIR)/text." | |
114 | ||
115 | man: | |
116 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | |
117 | @echo | |
118 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." | |
119 | ||
120 | texinfo: | |
121 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |
122 | @echo | |
123 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | |
124 | @echo "Run \`make' in that directory to run these through makeinfo" \ | |
125 | "(use \`make info' here to do that automatically)." | |
126 | ||
127 | info: | |
128 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |
129 | @echo "Running Texinfo files through makeinfo..." | |
130 | make -C $(BUILDDIR)/texinfo info | |
131 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | |
132 | ||
133 | gettext: | |
134 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | |
135 | @echo | |
136 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | |
137 | ||
138 | changes: | |
139 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | |
140 | @echo | |
141 | @echo "The overview file is in $(BUILDDIR)/changes." | |
142 | ||
143 | linkcheck: | |
144 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | |
145 | @echo | |
146 | @echo "Link check complete; look for any errors in the above output " \ | |
147 | "or in $(BUILDDIR)/linkcheck/output.txt." | |
148 | ||
149 | doctest: | |
150 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | |
151 | @echo "Testing of doctests in the sources finished, look at the " \ | |
152 | "results in $(BUILDDIR)/doctest/output.txt." |
0 | Straight Plugin API | |
1 | =================== | |
2 | ||
3 | Loaders | |
4 | ####### | |
5 | ||
6 | .. autofunction:: straight.plugin.loaders.unified_load | |
7 | .. autoclass:: straight.plugin.loaders.Loader | |
8 | .. autoclass:: straight.plugin.loaders.ModuleLoader | |
9 | .. autoclass:: straight.plugin.loaders.ObjectLoader | |
10 | .. autoclass:: straight.plugin.loaders.ClassLoader | |
11 | ||
12 | .. _api-plugin-manager: | |
13 | ||
14 | PluginManager | |
15 | ############# | |
16 | ||
17 | .. autoclass:: straight.plugin.manager.PluginManager | |
18 | :members: produce, call, first, pipe, |
0 | # -*- coding: utf-8 -*- | |
1 | # | |
2 | # straight.plugin documentation build configuration file, created by | |
3 | # sphinx-quickstart on Wed Jan 25 22:49:22 2012. | |
4 | # | |
5 | # This file is execfile()d with the current directory set to its containing dir. | |
6 | # | |
7 | # Note that not all possible configuration values are present in this | |
8 | # autogenerated file. | |
9 | # | |
10 | # All configuration values have a default; values that are commented out | |
11 | # serve to show the default. | |
12 | ||
13 | import sys, os | |
14 | ||
15 | # If extensions (or modules to document with autodoc) are in another directory, | |
16 | # add these directories to sys.path here. If the directory is relative to the | |
17 | # documentation root, use os.path.abspath to make it absolute, like shown here. | |
18 | #sys.path.insert(0, os.path.abspath('.')) | |
19 | ||
20 | # -- General configuration ----------------------------------------------------- | |
21 | ||
22 | # If your documentation needs a minimal Sphinx version, state it here. | |
23 | #needs_sphinx = '1.0' | |
24 | ||
25 | # Add any Sphinx extension module names here, as strings. They can be extensions | |
26 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | |
27 | extensions = [ | |
28 | 'sphinx.ext.autodoc', | |
29 | ] | |
30 | ||
31 | # Add any paths that contain templates here, relative to this directory. | |
32 | templates_path = ['_templates'] | |
33 | ||
34 | # The suffix of source filenames. | |
35 | source_suffix = '.rst' | |
36 | ||
37 | # The encoding of source files. | |
38 | #source_encoding = 'utf-8-sig' | |
39 | ||
40 | # The master toctree document. | |
41 | master_doc = 'index' | |
42 | ||
43 | # General information about the project. | |
44 | project = u'straight.plugin' | |
45 | copyright = u'2012, Calvin Spealman' | |
46 | ||
47 | # The version info for the project you're documenting, acts as replacement for | |
48 | # |version| and |release|, also used in various other places throughout the | |
49 | # built documents. | |
50 | # | |
51 | # The short X.Y version. | |
52 | version = '1.4' | |
53 | # The full version, including alpha/beta/rc tags. | |
54 | release = '1.4.0' | |
55 | ||
56 | # The language for content autogenerated by Sphinx. Refer to documentation | |
57 | # for a list of supported languages. | |
58 | #language = None | |
59 | ||
60 | # There are two options for replacing |today|: either, you set today to some | |
61 | # non-false value, then it is used: | |
62 | #today = '' | |
63 | # Else, today_fmt is used as the format for a strftime call. | |
64 | #today_fmt = '%B %d, %Y' | |
65 | ||
66 | # List of patterns, relative to source directory, that match files and | |
67 | # directories to ignore when looking for source files. | |
68 | exclude_patterns = ['_build'] | |
69 | ||
70 | # The reST default role (used for this markup: `text`) to use for all documents. | |
71 | #default_role = None | |
72 | ||
73 | # If true, '()' will be appended to :func: etc. cross-reference text. | |
74 | #add_function_parentheses = True | |
75 | ||
76 | # If true, the current module name will be prepended to all description | |
77 | # unit titles (such as .. function::). | |
78 | #add_module_names = True | |
79 | ||
80 | # If true, sectionauthor and moduleauthor directives will be shown in the | |
81 | # output. They are ignored by default. | |
82 | #show_authors = False | |
83 | ||
84 | # The name of the Pygments (syntax highlighting) style to use. | |
85 | pygments_style = 'sphinx' | |
86 | ||
87 | # A list of ignored prefixes for module index sorting. | |
88 | #modindex_common_prefix = [] | |
89 | ||
90 | ||
91 | # -- Options for HTML output --------------------------------------------------- | |
92 | ||
93 | # The theme to use for HTML and HTML Help pages. See the documentation for | |
94 | # a list of builtin themes. | |
95 | html_theme = 'default' | |
96 | ||
97 | # Theme options are theme-specific and customize the look and feel of a theme | |
98 | # further. For a list of options available for each theme, see the | |
99 | # documentation. | |
100 | #html_theme_options = {} | |
101 | ||
102 | # Add any paths that contain custom themes here, relative to this directory. | |
103 | #html_theme_path = [] | |
104 | ||
105 | # The name for this set of Sphinx documents. If None, it defaults to | |
106 | # "<project> v<release> documentation". | |
107 | #html_title = None | |
108 | ||
109 | # A shorter title for the navigation bar. Default is the same as html_title. | |
110 | #html_short_title = None | |
111 | ||
112 | # The name of an image file (relative to this directory) to place at the top | |
113 | # of the sidebar. | |
114 | #html_logo = None | |
115 | ||
116 | # The name of an image file (within the static path) to use as favicon of the | |
117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 | |
118 | # pixels large. | |
119 | #html_favicon = None | |
120 | ||
121 | # Add any paths that contain custom static files (such as style sheets) here, | |
122 | # relative to this directory. They are copied after the builtin static files, | |
123 | # so a file named "default.css" will overwrite the builtin "default.css". | |
124 | html_static_path = ['_static'] | |
125 | ||
126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |
127 | # using the given strftime format. | |
128 | #html_last_updated_fmt = '%b %d, %Y' | |
129 | ||
130 | # If true, SmartyPants will be used to convert quotes and dashes to | |
131 | # typographically correct entities. | |
132 | #html_use_smartypants = True | |
133 | ||
134 | # Custom sidebar templates, maps document names to template names. | |
135 | #html_sidebars = {} | |
136 | ||
137 | # Additional templates that should be rendered to pages, maps page names to | |
138 | # template names. | |
139 | #html_additional_pages = {} | |
140 | ||
141 | # If false, no module index is generated. | |
142 | #html_domain_indices = True | |
143 | ||
144 | # If false, no index is generated. | |
145 | #html_use_index = True | |
146 | ||
147 | # If true, the index is split into individual pages for each letter. | |
148 | #html_split_index = False | |
149 | ||
150 | # If true, links to the reST sources are added to the pages. | |
151 | #html_show_sourcelink = True | |
152 | ||
153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | |
154 | #html_show_sphinx = True | |
155 | ||
156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | |
157 | #html_show_copyright = True | |
158 | ||
159 | # If true, an OpenSearch description file will be output, and all pages will | |
160 | # contain a <link> tag referring to it. The value of this option must be the | |
161 | # base URL from which the finished HTML is served. | |
162 | #html_use_opensearch = '' | |
163 | ||
164 | # This is the file name suffix for HTML files (e.g. ".xhtml"). | |
165 | #html_file_suffix = None | |
166 | ||
167 | # Output file base name for HTML help builder. | |
168 | htmlhelp_basename = 'straightplugindoc' | |
169 | ||
170 | ||
171 | # -- Options for LaTeX output -------------------------------------------------- | |
172 | ||
173 | latex_elements = { | |
174 | # The paper size ('letterpaper' or 'a4paper'). | |
175 | #'papersize': 'letterpaper', | |
176 | ||
177 | # The font size ('10pt', '11pt' or '12pt'). | |
178 | #'pointsize': '10pt', | |
179 | ||
180 | # Additional stuff for the LaTeX preamble. | |
181 | #'preamble': '', | |
182 | } | |
183 | ||
184 | # Grouping the document tree into LaTeX files. List of tuples | |
185 | # (source start file, target name, title, author, documentclass [howto/manual]). | |
186 | latex_documents = [ | |
187 | ('index', 'straightplugin.tex', u'straight.plugin Documentation', | |
188 | u'Calvin Spealman', 'manual'), | |
189 | ] | |
190 | ||
191 | # The name of an image file (relative to this directory) to place at the top of | |
192 | # the title page. | |
193 | #latex_logo = None | |
194 | ||
195 | # For "manual" documents, if this is true, then toplevel headings are parts, | |
196 | # not chapters. | |
197 | #latex_use_parts = False | |
198 | ||
199 | # If true, show page references after internal links. | |
200 | #latex_show_pagerefs = False | |
201 | ||
202 | # If true, show URL addresses after external links. | |
203 | #latex_show_urls = False | |
204 | ||
205 | # Documents to append as an appendix to all manuals. | |
206 | #latex_appendices = [] | |
207 | ||
208 | # If false, no module index is generated. | |
209 | #latex_domain_indices = True | |
210 | ||
211 | ||
212 | # -- Options for manual page output -------------------------------------------- | |
213 | ||
214 | # One entry per manual page. List of tuples | |
215 | # (source start file, name, description, authors, manual section). | |
216 | man_pages = [ | |
217 | ('index', 'straightplugin', u'straight.plugin Documentation', | |
218 | [u'Calvin Spealman'], 1) | |
219 | ] | |
220 | ||
221 | # If true, show URL addresses after external links. | |
222 | #man_show_urls = False | |
223 | ||
224 | ||
225 | # -- Options for Texinfo output ------------------------------------------------ | |
226 | ||
227 | # Grouping the document tree into Texinfo files. List of tuples | |
228 | # (source start file, target name, title, author, | |
229 | # dir menu entry, description, category) | |
230 | texinfo_documents = [ | |
231 | ('index', 'straightplugin', u'straight.plugin Documentation', | |
232 | u'Calvin Spealman', 'straightplugin', 'One line description of project.', | |
233 | 'Miscellaneous'), | |
234 | ] | |
235 | ||
236 | # Documents to append as an appendix to all manuals. | |
237 | #texinfo_appendices = [] | |
238 | ||
239 | # If false, no module index is generated. | |
240 | #texinfo_domain_indices = True | |
241 | ||
242 | # How to display URL addresses: 'footnote', 'no', or 'inline'. | |
243 | #texinfo_show_urls = 'footnote' |
0 | Getting Started | |
1 | =============== | |
2 | ||
3 | Install | |
4 | ^^^^^^^ | |
5 | ||
6 | :: | |
7 | ||
8 | pip install straight.plugin | |
9 | ||
10 | That was super easy. | |
11 | ||
12 | Decide on a Namespace | |
13 | ^^^^^^^^^^^^^^^^^^^^^ | |
14 | ||
15 | You'll want to decide on a :term:`namespace` within your package where you'll | |
16 | keep your own plugins and where other developers can add more plugins for | |
17 | your package to use. | |
18 | ||
19 | For example, if you're writing a log filtering library named ``logfilter`` you may | |
20 | choose ``logfilter.plugins`` as a package to hold your plugins, so you'll create | |
21 | the empty package as you would any other python package. However, the only | |
22 | contents of ``logfilter/plugins/__init__.py`` will be a little bit of special | |
23 | code telling python this is a :term:`namespace package`. | |
24 | ||
25 | :: | |
26 | ||
27 | # This file will not be needed in Python 3.3 | |
28 | from pkgutil import extend_path | |
29 | __path__ = extend_path(__path__, __name__) | |
30 | ||
31 | ||
32 | Now, any modules you place in this package are plugin modules able to be loaded | |
33 | by ``straight.plugin``. | |
34 | ||
35 | :: | |
36 | ||
37 | from straight.plugin import load | |
38 | ||
39 | plugins = load("logfilter.plugins") | |
40 | ||
41 | If you'll be using more plugins than writing them, you should | |
42 | :doc:`read more <loaders>` about the loaders available and how they work. | |
43 | ||
44 | ||
45 | Write a Plugin | |
46 | ^^^^^^^^^^^^^^ | |
47 | ||
48 | Writing a plugin is even easier than loading them. There are two important | |
49 | plugin types to learn: Module plugins and class Plugins. Every module in | |
50 | your :term:`namespace package` is a module plugin. Every class they define | |
51 | is a class plugin. | |
52 | ||
53 | When you load module plugins, you get all of them. | |
54 | ||
55 | When you load class plugins, you filter them by a common base and only get | |
56 | those class plugins which inherit it. | |
57 | ||
58 | Module plugins are simple and usually define a few functions with names | |
59 | expected by whoever is loading and using the plugins. | |
60 | ||
61 | :: | |
62 | ||
63 | # This is a module plugin | |
64 | ||
65 | def add_extra(data): | |
66 | if 'x' in data and 'y' in data: | |
67 | data['z'] = x * y | |
68 | ||
69 | # This was a fairly useless plugin | |
70 | ||
71 | Class plugins are only a little longer, but can be a bit more controlled to | |
72 | work with. They depend on a common class the plugins inherit, and this would | |
73 | be defined by the project loading and using the plugins. | |
74 | ||
75 | :: | |
76 | ||
77 | # This is a class plugin | |
78 | ||
79 | class RstContentParser(ContentPlugin): | |
80 | """Parses any .rst files in a bundle.""" | |
81 | ||
82 | extensions = ('.rst',) | |
83 | ||
84 | def parse(self, content_file): | |
85 | src = content_file.read() | |
86 | return self.parse_string(src) | |
87 | ||
88 | def parse_string(self, src): | |
89 | parts = publish_parts(source=src, writer_name='html') | |
90 | return parts['html_body'] | |
91 | ||
92 | You can fit as many class plugins inside a module plugin as you want, and | |
93 | to load them instead of the modules you simply pass a ``subclasses`` | |
94 | parameter to ``load()``. | |
95 | ||
96 | :: | |
97 | ||
98 | from straight.plugin import load | |
99 | ||
100 | plugins = load("jules.plugins", subclasses=ContentPlugin) | |
101 | ||
102 | The resulting set of plugins are all the classes found which inherit from | |
103 | ContentPlugin. You can do whatever you want with these, but there are some | |
104 | helpful tools to make it easier to work with Class plugins. | |
105 | ||
106 | You can easily create instances of all the classes, which gives you a set | |
107 | of Instance plugins. | |
108 | ||
109 | :: | |
110 | ||
111 | instances = plugins.produce() | |
112 | ||
113 | You can even pass initialization parameters to ``produce()`` and they'll | |
114 | be used when creating instances of all the classes. You can see the | |
115 | :ref:`API docs <api-plugin-manager>` for the ``PluginManager`` to see the | |
116 | other ways you can work with groups of plugins. |
0 | Glossary | |
1 | ------------- | |
2 | ||
3 | .. glossary:: | |
4 | :sorted: | |
5 | ||
6 | package | |
7 | A Python package is a module defined by a directory, containing | |
8 | a ``__init__.py`` file, and can contain other modules or other | |
9 | packages within it. | |
10 | ||
11 | :: | |
12 | ||
13 | package/ | |
14 | __init__.py | |
15 | subpackage/ | |
16 | __init__.py | |
17 | submodule.py | |
18 | ||
19 | see also, :term:`namespace package` | |
20 | ||
21 | distribution | |
22 | Separately installable sets of Python modules as stored in the | |
23 | Python package index, and installed by distutils or setuptools. | |
24 | ||
25 | *definition taken from* `PEP 382`_ *text* | |
26 | ||
27 | vendor package | |
28 | Groups of files installed by an operating system's packaging | |
29 | mechanism (e.g. Debian or Redhat packages install on Linux systems). | |
30 | ||
31 | *definition taken from* `PEP 382`_ *text* | |
32 | ||
33 | namespace package | |
34 | Mechanism for splitting a single Python package across multiple | |
35 | directories on disk. One or more distributions (see :term:`distribution`) | |
36 | may provide modules which exist inside the same :term:`namespace package`. | |
37 | ||
38 | *definition taken from* `PEP 382`_ *text* | |
39 | ||
40 | module | |
41 | An importable python namespace defined in a single file. | |
42 | ||
43 | ||
44 | .. _PEP 382: http://www.python.org/dev/peps/pep-0382/ |
0 | .. straight.plugin documentation master file, created by | |
1 | sphinx-quickstart on Wed Jan 25 22:49:22 2012. | |
2 | You can adapt this file completely to your liking, but it should at least | |
3 | contain the root `toctree` directive. | |
4 | ||
5 | Welcome to straight.plugin's documentation! | |
6 | =========================================== | |
7 | ||
8 | .. toctree:: | |
9 | :maxdepth: 1 | |
10 | ||
11 | Getting Started <getting-started> | |
12 | Writing Plugins <write-plugin> | |
13 | Using Plugins <loaders> | |
14 | API <api> | |
15 | Glossary <glossary> | |
16 | ||
17 | ||
18 | Overview | |
19 | ======== | |
20 | ||
21 | Straight Plugin is very easy. | |
22 | ||
23 | Straight Plugin provides a type of plugin you can create | |
24 | from almost any existing Python modules, and an easy way for outside developers | |
25 | to add functionality and customization to your projects with their own | |
26 | plugins. | |
27 | ||
28 | Using any available plugins is a snap. | |
29 | ||
30 | :: | |
31 | ||
32 | from straight.plugin import load | |
33 | ||
34 | plugins = load('theproject.plugins', subclasses=FileHandler) | |
35 | ||
36 | handlers = plugins.produce() | |
37 | for line in open(filename): | |
38 | print handlers.pipe(line) | |
39 | ||
40 | ||
41 | And, writing plugins is just as easy. | |
42 | ||
43 | :: | |
44 | ||
45 | from theproject import FileHandler | |
46 | ||
47 | class LineNumbers(FileHandler): | |
48 | def __init__(self): | |
49 | self.lineno = 0 | |
50 | def pipe(line): | |
51 | self.lineno += 1 | |
52 | return "%04d %s" % (self.lineno, line) | |
53 | ||
54 | ||
55 | Plugins are found from a :term:`namespace`, which means the above example | |
56 | would find any ``FileHandler`` classes defined in modules you might import | |
57 | as ``theproject.plugins.default`` or ``theproject.plugins.extra``. Through | |
58 | the magic of :term:`namespace packages <namespace package>`, we can even | |
59 | split these up into separate installations, even managed by different teams. | |
60 | This means you can ship a project with a set of default plugins implementing | |
61 | its behavior, and allow other projects to hook in new functionality simply | |
62 | by shipping their own plugins under the same :term:`namespace`. | |
63 | ||
64 | :doc:`Get started and learn more, today <getting-started>` | |
65 | ||
66 | ||
67 | More Resources | |
68 | ############## | |
69 | ||
70 | * Full Documentation: http://readthedocs.org/docs/straightplugin/ | |
71 | ||
72 | * Mailing List: https://groups.google.com/forum/#!forum/straight.plugin | |
73 | ||
74 | ||
75 | Indices and tables | |
76 | ================== | |
77 | ||
78 | * :ref:`genindex` | |
79 | * :ref:`modindex` | |
80 | * :ref:`search` | |
81 |
0 | Plugin Loaders | |
1 | ============== | |
2 | ||
3 | Currently, three simple loaders are provided. | |
4 | ||
5 | * The :ref:`ModuleLoader <moduleloader>` simply loads the modules found | |
6 | * The :ref:`ClassLoader <classloader>` loads the subclasses of a given type | |
7 | * The :ref:`ObjectLoader <objectloader>` loads arbitrary objects from the modules | |
8 | ||
9 | .. _classloader: | |
10 | ||
11 | ClassLoader | |
12 | ----------- | |
13 | ||
14 | The recommended loader is the ``ClassLoader``, used to load all the | |
15 | classes from all of the modules in the namespace given. Optionally, | |
16 | you can pass a ``subclasses`` parameter to ``load()``, which will | |
17 | filter the loaded classes to those which are a sub-class of any given | |
18 | type. | |
19 | ||
20 | For example, | |
21 | ||
22 | :: | |
23 | ||
24 | import os | |
25 | from straight.plugin.loaders import ClassLoader | |
26 | from myapp import FileHandler | |
27 | ||
28 | plugins = ClassLoader().load('myplugins', subclasses=FileHandler) | |
29 | ||
30 | for filename in os.listdir('.'): | |
31 | for handler_cls in plugins: | |
32 | handler = handler_cls(filename) | |
33 | if handler.valid(): | |
34 | handler.process() | |
35 | ||
36 | However, it is preferred that you use the ``load()`` helper provided. | |
37 | ||
38 | :: | |
39 | ||
40 | from straight.plugin import load | |
41 | ||
42 | plugins = load('myplugins', subclasses=FileHandler) | |
43 | ||
44 | This will automatically use the ``ClassLoader`` when given a ``subclasses`` | |
45 | argument. | |
46 | ||
47 | .. _moduleloader: | |
48 | ||
49 | ModuleLoader | |
50 | ------------ | |
51 | ||
52 | Before anything else, ``straight.plugin`` loads modules. The | |
53 | ``ModuleLoader`` is used to do this. | |
54 | ||
55 | :: | |
56 | ||
57 | from straight.plugin.loaders import ModuleLoader | |
58 | ||
59 | plugins = ModuleLoader().load('myplugins') | |
60 | ||
61 | A note about `PEP-420 <http://www.python.org/dev/peps/pep-0420/>`_: | |
62 | ||
63 | Python 3.3 will support a new type of package, the Namespace Package. This | |
64 | allows language-level support for the namespaces that make ``straight.plugin`` | |
65 | work and when 3.3 lands, you can create addition plugins to be found in a | |
66 | namespace. For now, continue to use the ``pkgutil`` boilerplate, but when | |
67 | 3.3 is released, ``straight.plugin`` already supports both forms of | |
68 | namespace package! | |
69 | ||
70 | .. _objectloader: | |
71 | ||
72 | ObjectLoader | |
73 | ------------ | |
74 | ||
75 | If you need to combine multiple plugins inside each module, you can | |
76 | load all the objects from the modules, rather than the modules themselves. | |
77 | ||
78 | :: | |
79 | ||
80 | from straight.plugin.loaders import ObjectLoader | |
81 | ||
82 | plugins = ObjectLoader().load('myplugins') | |
83 | ||
84 |
0 | @ECHO OFF | |
1 | ||
2 | REM Command file for Sphinx documentation | |
3 | ||
4 | if "%SPHINXBUILD%" == "" ( | |
5 | set SPHINXBUILD=sphinx-build | |
6 | ) | |
7 | set BUILDDIR=_build | |
8 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | |
9 | set I18NSPHINXOPTS=%SPHINXOPTS% . | |
10 | if NOT "%PAPER%" == "" ( | |
11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | |
12 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% | |
13 | ) | |
14 | ||
15 | if "%1" == "" goto help | |
16 | ||
17 | if "%1" == "help" ( | |
18 | :help | |
19 | echo.Please use `make ^<target^>` where ^<target^> is one of | |
20 | echo. html to make standalone HTML files | |
21 | echo. dirhtml to make HTML files named index.html in directories | |
22 | echo. singlehtml to make a single large HTML file | |
23 | echo. pickle to make pickle files | |
24 | echo. json to make JSON files | |
25 | echo. htmlhelp to make HTML files and a HTML help project | |
26 | echo. qthelp to make HTML files and a qthelp project | |
27 | echo. devhelp to make HTML files and a Devhelp project | |
28 | echo. epub to make an epub | |
29 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter | |
30 | echo. text to make text files | |
31 | echo. man to make manual pages | |
32 | echo. texinfo to make Texinfo files | |
33 | echo. gettext to make PO message catalogs | |
34 | echo. changes to make an overview over all changed/added/deprecated items | |
35 | echo. linkcheck to check all external links for integrity | |
36 | echo. doctest to run all doctests embedded in the documentation if enabled | |
37 | goto end | |
38 | ) | |
39 | ||
40 | if "%1" == "clean" ( | |
41 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i | |
42 | del /q /s %BUILDDIR%\* | |
43 | goto end | |
44 | ) | |
45 | ||
46 | if "%1" == "html" ( | |
47 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html | |
48 | if errorlevel 1 exit /b 1 | |
49 | echo. | |
50 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. | |
51 | goto end | |
52 | ) | |
53 | ||
54 | if "%1" == "dirhtml" ( | |
55 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml | |
56 | if errorlevel 1 exit /b 1 | |
57 | echo. | |
58 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. | |
59 | goto end | |
60 | ) | |
61 | ||
62 | if "%1" == "singlehtml" ( | |
63 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml | |
64 | if errorlevel 1 exit /b 1 | |
65 | echo. | |
66 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. | |
67 | goto end | |
68 | ) | |
69 | ||
70 | if "%1" == "pickle" ( | |
71 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle | |
72 | if errorlevel 1 exit /b 1 | |
73 | echo. | |
74 | echo.Build finished; now you can process the pickle files. | |
75 | goto end | |
76 | ) | |
77 | ||
78 | if "%1" == "json" ( | |
79 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json | |
80 | if errorlevel 1 exit /b 1 | |
81 | echo. | |
82 | echo.Build finished; now you can process the JSON files. | |
83 | goto end | |
84 | ) | |
85 | ||
86 | if "%1" == "htmlhelp" ( | |
87 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp | |
88 | if errorlevel 1 exit /b 1 | |
89 | echo. | |
90 | echo.Build finished; now you can run HTML Help Workshop with the ^ | |
91 | .hhp project file in %BUILDDIR%/htmlhelp. | |
92 | goto end | |
93 | ) | |
94 | ||
95 | if "%1" == "qthelp" ( | |
96 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp | |
97 | if errorlevel 1 exit /b 1 | |
98 | echo. | |
99 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ | |
100 | .qhcp project file in %BUILDDIR%/qthelp, like this: | |
101 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\straightplugin.qhcp | |
102 | echo.To view the help file: | |
103 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\straightplugin.ghc | |
104 | goto end | |
105 | ) | |
106 | ||
107 | if "%1" == "devhelp" ( | |
108 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp | |
109 | if errorlevel 1 exit /b 1 | |
110 | echo. | |
111 | echo.Build finished. | |
112 | goto end | |
113 | ) | |
114 | ||
115 | if "%1" == "epub" ( | |
116 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub | |
117 | if errorlevel 1 exit /b 1 | |
118 | echo. | |
119 | echo.Build finished. The epub file is in %BUILDDIR%/epub. | |
120 | goto end | |
121 | ) | |
122 | ||
123 | if "%1" == "latex" ( | |
124 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | |
125 | if errorlevel 1 exit /b 1 | |
126 | echo. | |
127 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. | |
128 | goto end | |
129 | ) | |
130 | ||
131 | if "%1" == "text" ( | |
132 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text | |
133 | if errorlevel 1 exit /b 1 | |
134 | echo. | |
135 | echo.Build finished. The text files are in %BUILDDIR%/text. | |
136 | goto end | |
137 | ) | |
138 | ||
139 | if "%1" == "man" ( | |
140 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man | |
141 | if errorlevel 1 exit /b 1 | |
142 | echo. | |
143 | echo.Build finished. The manual pages are in %BUILDDIR%/man. | |
144 | goto end | |
145 | ) | |
146 | ||
147 | if "%1" == "texinfo" ( | |
148 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo | |
149 | if errorlevel 1 exit /b 1 | |
150 | echo. | |
151 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. | |
152 | goto end | |
153 | ) | |
154 | ||
155 | if "%1" == "gettext" ( | |
156 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale | |
157 | if errorlevel 1 exit /b 1 | |
158 | echo. | |
159 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. | |
160 | goto end | |
161 | ) | |
162 | ||
163 | if "%1" == "changes" ( | |
164 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes | |
165 | if errorlevel 1 exit /b 1 | |
166 | echo. | |
167 | echo.The overview file is in %BUILDDIR%/changes. | |
168 | goto end | |
169 | ) | |
170 | ||
171 | if "%1" == "linkcheck" ( | |
172 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck | |
173 | if errorlevel 1 exit /b 1 | |
174 | echo. | |
175 | echo.Link check complete; look for any errors in the above output ^ | |
176 | or in %BUILDDIR%/linkcheck/output.txt. | |
177 | goto end | |
178 | ) | |
179 | ||
180 | if "%1" == "doctest" ( | |
181 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest | |
182 | if errorlevel 1 exit /b 1 | |
183 | echo. | |
184 | echo.Testing of doctests in the sources finished, look at the ^ | |
185 | results in %BUILDDIR%/doctest/output.txt. | |
186 | goto end | |
187 | ) | |
188 | ||
189 | :end |
0 | Writing Plugins | |
1 | =============== | |
2 | ||
3 | Plugins can exist inside your | |
4 | existing packages or in special namespace packages, which exist | |
5 | only to house plugins. | |
6 | ||
7 | The only requirement is that any package containing plugins be | |
8 | designated a "namespace package", which is currently performed | |
9 | in Python via the ``pkgutil.extend_path`` utility, seen below. | |
10 | This allows the namespace to be provided in multiple places on | |
11 | the python ``sys.path``, where ``import`` looks, and all the | |
12 | contents will be combined. | |
13 | ||
14 | Use a :term:`namespace package` | |
15 | ||
16 | This allows multiple packages installed on your system to share | |
17 | this name, so they may come from different installed projects | |
18 | and all combine to provide a larger set of plugins. | |
19 | ||
20 | ||
21 | Example | |
22 | ------- | |
23 | ||
24 | :: | |
25 | ||
26 | # logfilter/__init__.py | |
27 | ||
28 | from pkgutil import extend_path | |
29 | __path__ = extend_path(__path__, __name__) | |
30 | ||
31 | ||
32 | :: | |
33 | ||
34 | # logfilter/hide_extra.py | |
35 | ||
36 | from logfilter import Skip | |
37 | ||
38 | def filter(log_entry): | |
39 | level = log_entry.split(':', 1)[0] | |
40 | if level != 'EXTRA': | |
41 | return log_entry | |
42 | else: | |
43 | raise Skip() | |
44 | ||
45 | ||
46 | Using the plugin | |
47 | '''''''''''''''' | |
48 | ||
49 | In our log tool, we might load all the modules in the ``logfilter`` | |
50 | namespace, and then use them all to process each entry in our logs. | |
51 | We don't need to know all the filters ahead of time, and other packages | |
52 | can be installed on a user's system providing additional modules | |
53 | in the namespace, which we never even knew about. | |
54 | ||
55 | :: | |
56 | ||
57 | from straight.plugin import load | |
58 | ||
59 | class Skip(Exception): | |
60 | pass | |
61 | ||
62 | plugins = load('logfilter') | |
63 | ||
64 | def filter_entry(log_entry): | |
65 | for plugin in plugins: | |
66 | try: | |
67 | log_entry = plugin.filter(log_entry) | |
68 | except Skip: | |
69 | pass | |
70 | return log_entry | |
71 | ||
72 | ||
73 | Distributing Plugins | |
74 | '''''''''''''''''''' | |
75 | ||
76 | If you are writing plugins inside your own project to use, they'll be | |
77 | distributed like any other modules in your package. There is no extra work | |
78 | to do here. | |
79 | ||
80 | However, if you want to release and distribute plugins on their own, you'll | |
81 | need to tell your :term:`setup.py` about your :term:`namespace package`. | |
82 | ||
83 | :: | |
84 | ||
85 | setup( | |
86 | # ... | |
87 | namespace_packages = ['logfilter.plugins'] | |
88 | ) | |
89 | ||
90 | This will make sure when your plugins are installed alongside the original | |
91 | project, both are importable, even though they came from their own | |
92 | distributions. | |
93 | ||
94 | You can read more about this at the Distribute | |
95 | `documentation on namespace packages <http://packages.python.org/distribute/setuptools.html#namespace-packages>`_. |
0 | 0 | #!/usr/bin/env python |
1 | 1 | |
2 | import sys | |
2 | 3 | from setuptools import setup, find_packages |
3 | 4 | |
4 | INSTALL_REQUIRES = [] | |
5 | 5 | |
6 | try: | |
7 | import importlib | |
8 | except ImportError: | |
9 | INSTALL_REQUIRES.append('importlib') | |
10 | ||
11 | setup(name='straight.plugin', | |
12 | version='1.4.1', | |
13 | description='A simple namespaced plugin facility', | |
14 | author='Calvin Spealman', | |
15 | author_email='ironfroggy@gmail.com', | |
16 | url='https://github.com/ironfroggy/straight.plugin', | |
6 | setup( | |
7 | name="straight.plugin", | |
8 | version="1.4.2", | |
9 | description="A simple namespaced plugin facility", | |
10 | author="Calvin Spealman", | |
11 | author_email="ironfroggy@gmail.com", | |
12 | url="https://github.com/ironfroggy/straight.plugin", | |
17 | 13 | packages=find_packages(), |
18 | namespace_packages=['straight'], | |
19 | install_requires=INSTALL_REQUIRES, | |
14 | namespace_packages=["straight"], | |
20 | 15 | classifiers=[ |
21 | 'Programming Language :: Python :: 2', | |
22 | 'Programming Language :: Python :: 3', | |
23 | 'Environment :: Plugins', | |
24 | ] | |
16 | "Programming Language :: Python :: 2", | |
17 | "Programming Language :: Python :: 3", | |
18 | "Environment :: Plugins", | |
19 | ], | |
25 | 20 | ) |
0 | __import__('pkg_resources').declare_namespace(__name__) | |
0 | __import__("pkg_resources").declare_namespace(__name__) |
0 | 0 | """Facility to load plugins.""" |
1 | 1 | |
2 | import os | |
2 | 3 | import sys |
3 | import os | |
4 | 4 | |
5 | from functools import lru_cache | |
6 | from imp import find_module | |
5 | 7 | from importlib import import_module |
6 | from imp import find_module | |
7 | 8 | |
8 | 9 | from straight.plugin.manager import PluginManager |
10 | ||
11 | ||
12 | def unique_list(seq): | |
13 | seen = set() | |
14 | seen_add = seen.add | |
15 | return [x for x in seq if not (x in seen or seen_add(x))] | |
9 | 16 | |
10 | 17 | |
11 | 18 | class Loader(object): |
13 | 20 | |
14 | 21 | def __init__(self, *args, **kwargs): |
15 | 22 | self._cache = [] |
23 | self.loaded = False | |
24 | ||
25 | def _fill_cache(self, *args, **kwargs): | |
26 | raise NotImplementedError() | |
16 | 27 | |
17 | 28 | def load(self, *args, **kwargs): |
18 | self._fill_cache(*args, **kwargs) | |
19 | self._post_fill() | |
20 | self._order() | |
29 | if not self.loaded: | |
30 | self._fill_cache(*args, **kwargs) | |
31 | self._post_fill() | |
32 | self._order() | |
33 | self.loaded = True | |
21 | 34 | return PluginManager(self._cache) |
22 | 35 | |
23 | 36 | def _meta(self, plugin): |
24 | meta = getattr(plugin, '__plugin__', None) | |
37 | meta = getattr(plugin, "__plugin__", None) | |
25 | 38 | return meta |
26 | 39 | |
27 | 40 | def _post_fill(self): |
28 | 41 | for plugin in self._cache: |
29 | 42 | meta = self._meta(plugin) |
30 | if not getattr(meta, 'load', True): | |
43 | if not getattr(meta, "load", True): | |
31 | 44 | self._cache.remove(plugin) |
32 | for implied_namespace in getattr(meta, 'imply_plugins', []): | |
45 | for implied_namespace in getattr(meta, "imply_plugins", []): | |
33 | 46 | plugins = self._cache |
34 | 47 | self._cache = self.load(implied_namespace) |
35 | 48 | self._post_fill() |
43 | 56 | |
44 | 57 | def _plugin_priority(self, plugin): |
45 | 58 | meta = self._meta(plugin) |
46 | return getattr(meta, 'priority', 0.0) | |
59 | return getattr(meta, "priority", 0.0) | |
47 | 60 | |
48 | 61 | |
49 | 62 | class ModuleLoader(Loader): |
50 | 63 | """Performs the work of locating and loading straight plugins. |
51 | ||
64 | ||
52 | 65 | This looks for plugins in every location in the import path. |
53 | 66 | """ |
54 | 67 | |
57 | 70 | self.recurse = recurse |
58 | 71 | |
59 | 72 | def _isPackage(self, path): |
60 | pkg_init = os.path.join(path, '__init__.py') | |
61 | if os.path.exists(pkg_init): | |
62 | return True | |
63 | ||
64 | return False | |
73 | pkg_init = os.path.join(path, "__init__.py") | |
74 | return os.path.exists(pkg_init) | |
65 | 75 | |
66 | 76 | def _findPluginFilePaths(self, namespace): |
67 | 77 | already_seen = set() |
68 | 78 | |
69 | 79 | # Look in each location in the path |
70 | for path in sys.path: | |
80 | for path in set(sys.path): | |
71 | 81 | |
72 | 82 | # Within this, we want to look for a package for the namespace |
73 | 83 | namespace_rel_path = namespace.replace(".", os.path.sep) |
74 | 84 | namespace_path = os.path.join(path, namespace_rel_path) |
75 | if os.path.exists(namespace_path): | |
85 | try: | |
76 | 86 | for possible in os.listdir(namespace_path): |
77 | 87 | |
78 | 88 | poss_path = os.path.join(namespace_path, possible) |
79 | if os.path.isdir(poss_path): | |
80 | if not self._isPackage(poss_path): | |
81 | continue | |
89 | ||
90 | if self._isPackage(poss_path): | |
82 | 91 | if self.recurse: |
83 | subns = '.'.join((namespace, possible.split('.py')[0])) | |
92 | subns = ".".join((namespace, possible.split(".py")[0])) | |
84 | 93 | for path in self._findPluginFilePaths(subns): |
85 | 94 | yield path |
86 | 95 | base = possible |
87 | 96 | else: |
88 | 97 | base, ext = os.path.splitext(possible) |
89 | if base == '__init__' or ext != '.py': | |
98 | if base == "__init__" or ext != ".py": | |
90 | 99 | continue |
91 | ||
100 | ||
92 | 101 | if base not in already_seen: |
93 | 102 | already_seen.add(base) |
94 | 103 | yield os.path.join(namespace, possible) |
104 | except (FileNotFoundError, NotADirectoryError): | |
105 | pass | |
95 | 106 | |
96 | 107 | def _findPluginModules(self, namespace): |
97 | 108 | for filepath in self._findPluginFilePaths(namespace): |
98 | 109 | path_segments = list(filepath.split(os.path.sep)) |
99 | 110 | path_segments = [p for p in path_segments if p] |
100 | 111 | path_segments[-1] = os.path.splitext(path_segments[-1])[0] |
101 | import_path = '.'.join(path_segments) | |
112 | import_path = ".".join(path_segments) | |
102 | 113 | |
103 | 114 | try: |
104 | 115 | module = import_module(import_path) |
105 | 116 | except ImportError: |
106 | #raise Exception(import_path) | |
117 | # raise Exception(import_path) | |
107 | 118 | |
108 | 119 | module = None |
109 | 120 | |
121 | 132 | class ObjectLoader(Loader): |
122 | 133 | """Loads classes or objects out of modules in a namespace, based on a |
123 | 134 | provided criteria. |
124 | ||
135 | ||
125 | 136 | The load() method returns all objects exported by the module. |
126 | 137 | """ |
127 | 138 | |
128 | 139 | def __init__(self, recurse=False): |
140 | super().__init__() | |
129 | 141 | self.module_loader = ModuleLoader(recurse=recurse) |
130 | 142 | |
131 | 143 | def _fill_cache(self, namespace): |
134 | 146 | |
135 | 147 | for module in modules: |
136 | 148 | for attr_name in dir(module): |
137 | if not attr_name.startswith('_'): | |
149 | if not attr_name.startswith("_"): | |
138 | 150 | objects.append(getattr(module, attr_name)) |
139 | ||
151 | ||
140 | 152 | self._cache = objects |
141 | 153 | return objects |
142 | 154 | |
160 | 172 | return classes |
161 | 173 | |
162 | 174 | |
175 | @lru_cache(maxsize=None, typed=False) | |
163 | 176 | def unified_load(namespace, subclasses=None, recurse=False): |
164 | 177 | """Provides a unified interface to both the module and class loaders, |
165 | 178 | finding modules by default or classes if given a ``subclasses`` parameter. |
169 | 182 | return ClassLoader(recurse=recurse).load(namespace, subclasses=subclasses) |
170 | 183 | else: |
171 | 184 | return ModuleLoader(recurse=recurse).load(namespace) |
172 |
0 | 0 | class PluginManager(object): |
1 | ||
2 | 1 | def __init__(self, plugins): |
3 | 2 | self._plugins = plugins |
4 | 3 | |
49 | 48 | |
50 | 49 | Useful to utilize plugins as sets of filters. |
51 | 50 | """ |
52 | ||
51 | r = first_arg | |
53 | 52 | for plugin in self._plugins: |
54 | 53 | method = getattr(plugin, methodname, None) |
55 | 54 | if method is None: |
58 | 57 | if r is not None: |
59 | 58 | first_arg = r |
60 | 59 | return r |
61 |
0 | Metadata-Version: 2.1 | |
1 | Name: straight.plugin | |
2 | Version: 1.4.2 | |
3 | Summary: A simple namespaced plugin facility | |
4 | Home-page: https://github.com/ironfroggy/straight.plugin | |
5 | Author: Calvin Spealman | |
6 | Author-email: ironfroggy@gmail.com | |
7 | Classifier: Programming Language :: Python :: 2 | |
8 | Classifier: Programming Language :: Python :: 3 | |
9 | Classifier: Environment :: Plugins | |
10 | License-File: LICENSE | |
11 | License-File: AUTHORS |
0 | AUTHORS | |
1 | LICENSE | |
2 | MANIFEST.in | |
3 | README.rst | |
4 | setup.py | |
5 | tests.py | |
6 | straight/__init__.py | |
7 | straight.plugin.egg-info/PKG-INFO | |
8 | straight.plugin.egg-info/SOURCES.txt | |
9 | straight.plugin.egg-info/dependency_links.txt | |
10 | straight.plugin.egg-info/namespace_packages.txt | |
11 | straight.plugin.egg-info/top_level.txt | |
12 | straight/plugin/__init__.py | |
13 | straight/plugin/loaders.py | |
14 | straight/plugin/manager.py | |
15 | test-packages/class-test-plugins/testplugin/__init__.py | |
16 | test-packages/class-test-plugins/testplugin/testclasses.py | |
17 | test-packages/imply-plugins/testplugin/__init__.py | |
18 | test-packages/imply-plugins/testplugin/foo.py | |
19 | test-packages/imply-plugins/testplugin_2/__init__.py | |
20 | test-packages/imply-plugins/testplugin_2/bar.py | |
21 | test-packages/more-test-plugins/testplugin/__init__.py | |
22 | test-packages/more-test-plugins/testplugin/bar.py | |
23 | test-packages/package-test-plugins/testplugin/__init__.py | |
24 | test-packages/package-test-plugins/testplugin/bar/__init__.py | |
25 | test-packages/package-test-plugins/testplugin/baz/__init__.py | |
26 | test-packages/package-test-plugins/testplugin/baz/quu/__init__.py | |
27 | test-packages/package-test-plugins/testplugin/foo/__init__.py | |
28 | test-packages/pep-420-plugins/testplugin/foo.py | |
29 | test-packages/some-test-plugins/testplugin/__init__.py | |
30 | test-packages/some-test-plugins/testplugin/foo.py⏎ |
0 | straight |
0 | straight |
0 | 0 | class A(object): |
1 | 1 | class __plugin__: |
2 | 2 | priority = 0.5 |
3 | ||
3 | 4 | |
4 | 5 | class B(object): |
5 | 6 | class __plugin__: |
6 | 7 | priority = 1.0 |
7 | 8 | |
9 | ||
8 | 10 | class A1(A): |
9 | 11 | pass |
0 | 0 | class __plugin__: |
1 | imply_plugins = ( | |
2 | 'testplugin_2', | |
3 | ) | |
1 | imply_plugins = ("testplugin_2",) | |
4 | 2 | load = False |
3 | ||
4 | ||
5 | 5 | def do(x): |
6 | 6 | return x + 1 |
0 | 0 | #!/usr/bin/env python |
1 | 1 | |
2 | import os | |
2 | 3 | import sys |
3 | import os | |
4 | 4 | import unittest |
5 | 5 | from types import ModuleType |
6 | ||
7 | import mock | |
6 | from unittest import mock | |
8 | 7 | |
9 | 8 | from straight.plugin import loaders, manager |
10 | 9 | |
20 | 19 | Usually you can use TestResult.skip() or one of the skipping decorators |
21 | 20 | instead of raising this directly. |
22 | 21 | """ |
22 | ||
23 | 23 | def skipIf(cond, reason): |
24 | 24 | """ |
25 | 25 | Unconditionally skip a test. |
26 | 26 | """ |
27 | ||
27 | 28 | def decorator(test_item): |
28 | 29 | if cond: |
29 | 30 | if not isinstance(test_item, type): |
31 | ||
30 | 32 | @functools.wraps(test_item) |
31 | 33 | def skip_wrapper(*args, **kwargs): |
32 | 34 | pass |
35 | ||
33 | 36 | test_item = skip_wrapper |
34 | 37 | |
35 | 38 | test_item.__unittest_skip__ = True |
37 | 40 | return test_item |
38 | 41 | else: |
39 | 42 | return test_item |
43 | ||
40 | 44 | return decorator |
41 | 45 | |
42 | 46 | |
56 | 60 | for path in self.paths: |
57 | 61 | del sys.path[-1] |
58 | 62 | for modname in list(sys.modules): |
59 | if modname.startswith('testplugin'): | |
63 | if modname.startswith("testplugin"): | |
60 | 64 | del sys.modules[modname] |
61 | 65 | |
62 | 66 | |
63 | 67 | class ModuleLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
64 | 68 | |
65 | 69 | paths = ( |
66 | os.path.join(os.path.dirname(__file__), 'test-packages', 'more-test-plugins'), | |
67 | os.path.join(os.path.dirname(__file__), 'test-packages', 'some-test-plugins'), | |
68 | ) | |
69 | ||
70 | os.path.join(os.path.dirname(__file__), "test-packages", "more-test-plugins"), | |
71 | os.path.join(os.path.dirname(__file__), "test-packages", "some-test-plugins"), | |
72 | ) | |
73 | ||
70 | 74 | def setUp(self): |
71 | 75 | self.loader = loaders.ModuleLoader() |
72 | 76 | super(ModuleLoaderTestCase, self).setUp() |
73 | ||
77 | ||
74 | 78 | def test_load(self): |
75 | modules = list(self.loader.load('testplugin')) | |
79 | modules = list(self.loader.load("testplugin")) | |
76 | 80 | assert len(modules) == 2, modules |
77 | 81 | |
78 | 82 | def test_plugin(self): |
79 | assert self.loader.load('testplugin')[0].do(1) == 2 | |
83 | assert self.loader.load("testplugin")[0].do(1) == 2 | |
80 | 84 | |
81 | 85 | |
82 | 86 | class ImpliedNamespaceModuleTestCase(LoaderTestCaseMixin, unittest.TestCase): |
83 | 87 | |
84 | 88 | paths = ( |
85 | os.path.join(os.path.dirname(__file__), 'test-packages', 'pep-420-plugins'), | |
86 | ) | |
87 | ||
89 | os.path.join(os.path.dirname(__file__), "test-packages", "pep-420-plugins"), | |
90 | ) | |
91 | ||
88 | 92 | def setUp(self): |
89 | 93 | self.loader = loaders.ModuleLoader() |
90 | 94 | super(ImpliedNamespaceModuleTestCase, self).setUp() |
91 | ||
92 | @skipIf(sys.version_info < (3, 3),"Python < 3.3") | |
95 | ||
96 | @skipIf(sys.version_info < (3, 3), "Python < 3.3") | |
93 | 97 | def test_load(self): |
94 | modules = list(self.loader.load('testplugin')) | |
98 | modules = list(self.loader.load("testplugin")) | |
95 | 99 | assert len(modules) == 1, modules |
96 | 100 | |
97 | 101 | @skipIf(sys.version_info < (3, 3), "Python < 3.3") |
98 | 102 | def test_plugin(self): |
99 | r = self.loader.load('testplugin')[0].do("implied namespace packages are") | |
103 | r = self.loader.load("testplugin")[0].do("implied namespace packages are") | |
100 | 104 | self.assertEqual(r, "implied namespace packages are from pep-420") |
101 | 105 | |
102 | 106 | |
103 | 107 | class ImplyLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
104 | 108 | |
105 | paths = ( | |
106 | os.path.join(os.path.dirname(__file__), 'test-packages', 'imply-plugins'), | |
107 | ) | |
108 | ||
109 | paths = (os.path.join(os.path.dirname(__file__), "test-packages", "imply-plugins"),) | |
110 | ||
109 | 111 | def setUp(self): |
110 | 112 | self.loader = loaders.ModuleLoader() |
111 | 113 | super(ImplyLoaderTestCase, self).setUp() |
112 | ||
114 | ||
113 | 115 | def test_load(self): |
114 | modules = list(self.loader.load('testplugin')) | |
116 | modules = list(self.loader.load("testplugin")) | |
115 | 117 | assert len(modules) == 1, modules |
116 | assert modules[0].__name__ == 'testplugin_2.bar', modules[0].__name__ | |
118 | assert modules[0].__name__ == "testplugin_2.bar", modules[0].__name__ | |
117 | 119 | |
118 | 120 | |
119 | 121 | class ObjectLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
120 | 122 | |
121 | 123 | paths = ( |
122 | os.path.join(os.path.dirname(__file__), 'test-packages', 'more-test-plugins'), | |
123 | os.path.join(os.path.dirname(__file__), 'test-packages', 'some-test-plugins'), | |
124 | os.path.join(os.path.dirname(__file__), "test-packages", "more-test-plugins"), | |
125 | os.path.join(os.path.dirname(__file__), "test-packages", "some-test-plugins"), | |
124 | 126 | ) |
125 | 127 | |
126 | 128 | def setUp(self): |
128 | 130 | super(ObjectLoaderTestCase, self).setUp() |
129 | 131 | |
130 | 132 | def test_load_all(self): |
131 | objects = list(self.loader.load('testplugin')) | |
132 | self.assertEqual(len(objects), 2, str(objects)[:100] + ' ...') | |
133 | objects = list(self.loader.load("testplugin")) | |
134 | self.assertEqual(len(objects), 2, str(objects)[:100] + " ...") | |
133 | 135 | |
134 | 136 | |
135 | 137 | class ClassLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
136 | 138 | |
137 | 139 | paths = ( |
138 | os.path.join(os.path.dirname(__file__), 'test-packages', 'class-test-plugins'), | |
140 | os.path.join(os.path.dirname(__file__), "test-packages", "class-test-plugins"), | |
139 | 141 | ) |
140 | 142 | |
141 | 143 | def setUp(self): |
143 | 145 | super(ClassLoaderTestCase, self).setUp() |
144 | 146 | |
145 | 147 | def test_all_classes(self): |
146 | classes = list(self.loader.load('testplugin')) | |
148 | classes = list(self.loader.load("testplugin")) | |
147 | 149 | |
148 | 150 | self.assertEqual(len(classes), 3) |
149 | 151 | |
150 | 152 | def test_subclasses(self): |
151 | 153 | from testplugin import testclasses |
152 | classes = list(self.loader.load('testplugin', subclasses=testclasses.A)) | |
154 | ||
155 | classes = list(self.loader.load("testplugin", subclasses=testclasses.A)) | |
153 | 156 | |
154 | 157 | self.assertEqual(len(classes), 1) |
155 | 158 | self.assertTrue(classes[0] is testclasses.A1) |
156 | 159 | |
160 | ||
157 | 161 | class PriorityLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
158 | 162 | |
159 | 163 | paths = ( |
160 | os.path.join(os.path.dirname(__file__), 'test-packages', 'class-test-plugins'), | |
164 | os.path.join(os.path.dirname(__file__), "test-packages", "class-test-plugins"), | |
161 | 165 | ) |
162 | 166 | |
163 | 167 | def setUp(self): |
165 | 169 | super(PriorityLoaderTestCase, self).setUp() |
166 | 170 | |
167 | 171 | def test_all_classes(self): |
168 | classes = list(self.loader.load('testplugin')) | |
169 | ||
170 | self.assertEqual(classes[0].__name__, 'B') | |
171 | self.assertEqual(classes[1].__name__, 'A') | |
172 | self.assertEqual(classes[2].__name__, 'A1') | |
172 | classes = list(self.loader.load("testplugin")) | |
173 | ||
174 | self.assertEqual(classes[0].__name__, "B") | |
175 | self.assertEqual(classes[1].__name__, "A") | |
176 | self.assertEqual(classes[2].__name__, "A1") | |
177 | ||
173 | 178 | |
174 | 179 | class PackageLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
175 | 180 | paths = ( |
176 | os.path.join(os.path.dirname(__file__), 'test-packages', 'package-test-plugins'), | |
181 | os.path.join( | |
182 | os.path.dirname(__file__), "test-packages", "package-test-plugins" | |
183 | ), | |
177 | 184 | ) |
178 | 185 | |
179 | 186 | def setUp(self): |
180 | 187 | self.loader = loaders.ModuleLoader() |
181 | 188 | super(PackageLoaderTestCase, self).setUp() |
182 | ||
189 | ||
183 | 190 | def test_find_packages(self): |
184 | filepaths = list(self.loader._findPluginFilePaths('testplugin')) | |
191 | filepaths = list(self.loader._findPluginFilePaths("testplugin")) | |
185 | 192 | |
186 | 193 | self.assertEqual(len(filepaths), 3) |
187 | 194 | |
188 | ||
189 | 195 | def test_load_packages(self): |
190 | packages = list(self.loader.load('testplugin')) | |
196 | packages = list(self.loader.load("testplugin")) | |
191 | 197 | |
192 | 198 | self.assertEqual(len(packages), 3) |
193 | 199 | |
195 | 201 | self.assertTrue(isinstance(pkg, ModuleType)) |
196 | 202 | |
197 | 203 | def test_plugin(self): |
198 | plugins = self.loader.load('testplugin') | |
204 | plugins = self.loader.load("testplugin") | |
199 | 205 | |
200 | 206 | results = set(p.do(1) for p in plugins) |
201 | 207 | |
204 | 210 | |
205 | 211 | class RecursingPackageLoaderTestCase(LoaderTestCaseMixin, unittest.TestCase): |
206 | 212 | paths = ( |
207 | os.path.join(os.path.dirname(__file__), 'test-packages', 'package-test-plugins'), | |
213 | os.path.join( | |
214 | os.path.dirname(__file__), "test-packages", "package-test-plugins" | |
215 | ), | |
208 | 216 | ) |
209 | 217 | |
210 | 218 | def setUp(self): |
212 | 220 | super(RecursingPackageLoaderTestCase, self).setUp() |
213 | 221 | |
214 | 222 | def test_find_packages(self): |
215 | filepaths = list(self.loader._findPluginFilePaths('testplugin')) | |
223 | filepaths = list(self.loader._findPluginFilePaths("testplugin")) | |
216 | 224 | |
217 | 225 | self.assertEqual(len(filepaths), 4) |
218 | 226 | |
219 | ||
220 | 227 | def test_load_packages(self): |
221 | packages = list(self.loader.load('testplugin')) | |
228 | packages = list(self.loader.load("testplugin")) | |
222 | 229 | |
223 | 230 | self.assertEqual(len(packages), 4) |
224 | 231 | |
226 | 233 | self.assertTrue(isinstance(pkg, ModuleType)) |
227 | 234 | |
228 | 235 | def test_plugin(self): |
229 | plugins = self.loader.load('testplugin') | |
236 | plugins = self.loader.load("testplugin") | |
230 | 237 | |
231 | 238 | results = set(p.do(1) for p in plugins) |
232 | 239 | |
233 | self.assertEqual(results, set((2, 3, 4, 'quu'))) | |
240 | self.assertEqual(results, set((2, 3, 4, "quu"))) | |
234 | 241 | |
235 | 242 | |
236 | 243 | class PluginManagerTestCase(unittest.TestCase): |
237 | ||
238 | def setUp(self): | |
239 | self.m = manager.PluginManager([ | |
240 | mock.Mock(), | |
241 | mock.Mock(), | |
242 | ]) | |
244 | def setUp(self): | |
245 | self.m = manager.PluginManager([mock.Mock(), mock.Mock()]) | |
243 | 246 | |
244 | 247 | def test_first(self): |
245 | 248 | self.m._plugins[0].x.return_value = 1 |
246 | 249 | |
247 | self.assertEqual(1, self.m.first('x', 'a')) | |
250 | self.assertEqual(1, self.m.first("x", "a")) | |
248 | 251 | self.assertFalse(self.m._plugins[1].called) |
249 | self.assertTrue(self.m._plugins[0].called_with('a')) | |
252 | self.assertTrue(self.m._plugins[0].called_with("a")) | |
250 | 253 | |
251 | 254 | def test_pipe(self): |
252 | 255 | def plus_one(x): |
253 | 256 | return x + 1 |
257 | ||
254 | 258 | self.m._plugins[0].x.side_effect = plus_one |
255 | 259 | self.m._plugins[1].x.side_effect = plus_one |
256 | ||
257 | self.assertEqual(3, self.m.pipe('x', 1)) | |
260 | self.assertEqual(3, self.m.pipe("x", 1)) | |
261 | ||
262 | def test_pipe_no_plugins_found(self): | |
263 | no_plugins = manager.PluginManager([]) | |
264 | self.assertEqual(1, no_plugins.pipe("x", 1)) | |
258 | 265 | |
259 | 266 | def test_call(self): |
260 | results = self.m.call('x', 1) | |
261 | self.assertTrue(self.m._plugins[0].called_with('a')) | |
267 | results = self.m.call("x", 1) | |
268 | self.assertTrue(self.m._plugins[0].called_with("a")) | |
262 | 269 | self.assertTrue(self.m._plugins[1].x.called_with(1)) |
263 | 270 | |
264 | 271 | def test_produce(self): |
268 | 275 | assert products[1] is self.m._plugins[1].return_value |
269 | 276 | self.m._plugins[1].called_with(1, 2) |
270 | 277 | |
271 | if __name__ == '__main__': | |
278 | ||
279 | if __name__ == "__main__": | |
272 | 280 | unittest.main() |