├── .coveragerc
├── .github
└── workflows
│ └── automatic-tests.yml
├── .gitignore
├── .hgignore
├── .hgtags
├── .readthedocs.yml
├── README.md
├── package
├── CHANGELOG.txt
├── LICENSE.txt
├── MANIFEST.in
├── README.txt
├── TODO.txt
├── artwork
│ ├── LICENSE.txt
│ ├── yapsy-big.png
│ ├── yapsy-favicon.ico
│ ├── yapsy-favicon.png
│ ├── yapsy.png
│ └── yapsy.svg
├── doc
│ ├── Advices.rst
│ ├── AutoInstallPluginManager.rst
│ ├── ConfigurablePluginManager.rst
│ ├── Extensions.rst
│ ├── FilteredPluginManager.rst
│ ├── IMultiprocessChildPlugin.rst
│ ├── IPlugin.rst
│ ├── IPluginLocator.rst
│ ├── Makefile
│ ├── MultiprocessPluginManager.rst
│ ├── MultiprocessPluginProxy.rst
│ ├── PluginFileLocator.rst
│ ├── PluginInfo.rst
│ ├── PluginManager.rst
│ ├── PluginManagerDecorator.rst
│ ├── VersionedPluginManager.rst
│ ├── conf.py
│ ├── index.rst
│ └── make.bat
├── runtests.py
├── setup.py
├── test
│ ├── __init__.py
│ ├── plugins
│ │ ├── ConfigPlugin.py
│ │ ├── ErroneousPlugin.py
│ │ ├── LegacyMultiprocessPlugin.py
│ │ ├── SimpleMultiprocessPlugin.py
│ │ ├── SimplePlugin.py
│ │ ├── VersionedPlugin10.py
│ │ ├── VersionedPlugin11.py
│ │ ├── VersionedPlugin111.py
│ │ ├── VersionedPlugin12.py
│ │ ├── VersionedPlugin12a1.py
│ │ ├── configplugin.yapsy-config-plugin
│ │ ├── configplugin.yapsy-filter-plugin
│ │ ├── erroneousplugin.yapsy-error-plugin
│ │ ├── legacymultiprocessplugin.multiprocess-plugin
│ │ ├── simplemultiprocessplugin.multiprocess-plugin
│ │ ├── simpleplugin.yapsy-filter-plugin
│ │ ├── simpleplugin.yapsy-plugin
│ │ ├── versioned10.version-plugin
│ │ ├── versioned11.version-plugin
│ │ ├── versioned111.version-plugin
│ │ ├── versioned12.version-plugin
│ │ └── versioned12a1.version-plugin
│ ├── pluginsasdirs
│ │ ├── SimplePlugin
│ │ │ └── __init__.py
│ │ └── simpleplugin.yapsy-plugin
│ ├── pluginstoinstall
│ │ ├── AutoInstallPlugin.py
│ │ ├── autoinstallWRONGzipplugin.zip
│ │ ├── autoinstallZIPplugin.zip
│ │ ├── autoinstalldirplugin.yapsy-autoinstall-plugin
│ │ ├── autoinstalldirplugin
│ │ │ └── __init__.py
│ │ └── autoinstallplugin.yapsy-autoinstall-plugin
│ ├── test_All.py
│ ├── test_AutoInstallPlugin.py
│ ├── test_ConfigPlugin.py
│ ├── test_ErrorInPlugin.py
│ ├── test_FilterPlugin.py
│ ├── test_PluginFileLocator.py
│ ├── test_PluginInfo.py
│ ├── test_SimpleMultiprocessPlugin.py
│ ├── test_SimplePlugin.py
│ ├── test_Singleton.py
│ ├── test_VersionedPlugin.py
│ └── test_settings.py
└── yapsy
│ ├── AutoInstallPluginManager.py
│ ├── ConfigurablePluginManager.py
│ ├── FilteredPluginManager.py
│ ├── IMultiprocessChildPlugin.py
│ ├── IMultiprocessPlugin.py
│ ├── IPlugin.py
│ ├── IPluginLocator.py
│ ├── MultiprocessPluginManager.py
│ ├── MultiprocessPluginProxy.py
│ ├── PluginFileLocator.py
│ ├── PluginInfo.py
│ ├── PluginManager.py
│ ├── PluginManagerDecorator.py
│ ├── VersionedPluginManager.py
│ └── __init__.py
├── requirements-release.txt
├── requirements-tests.txt
└── utils
├── gen_doc.sh
├── release_env.sh
└── upload_packages.sh
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit = /tmp/*, _*,*/site-packages/*,*/test/*,*/distutils/*
3 |
4 | [report]
5 | exclude_lines =
6 | raise NotImplementedError
7 |
--------------------------------------------------------------------------------
/.github/workflows/automatic-tests.yml:
--------------------------------------------------------------------------------
1 | name: Automatic Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v4
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install pytest
23 | pip install -r requirements-tests.txt
24 | - name: Lint with flake8
25 | run: |
26 | # stop the build if there are Python syntax errors or undefined names
27 | flake8 ./package --count --select=E9,F63,F7,F82 --show-source --statistics
28 | # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
29 | # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
30 | - name: Test with pytest with coverage report
31 | run: |
32 | pytest --cov --cov-report=lcov
33 | - name: Coveralls
34 | uses: coverallsapp/github-action@master
35 | with:
36 | github-token: ${{ secrets.GITHUB_TOKEN }}
37 | path-to-lcov: coverage.lcov
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | *.swp
3 | __pycache__
4 | .coverage
5 | .installed.cfg
6 | .mr.developer.cfg
7 | bin/
8 | plugins/
9 | db/*
10 | log/*
11 | env3.4/
12 | dist/
13 | build/
14 | *.egg-info/
15 | .idea/
16 | develop-eggs/
17 | src/
18 | doc/src
19 | _html/
20 | eggs/
21 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | \.pyc$
2 | \.~$
3 |
4 | # use glob syntax.
5 | syntax: glob
6 | _build/*
7 | .project
8 | package/build/*
9 | package/dist/*
10 | package/Yapsy.egg-info/*
11 | htmlcov/*
12 | .coverage
13 | release_env/*
14 | .direnv/*
15 | .envrc
16 |
17 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | 27a222ccc739de48d114ae282f848aad27775904 SubverionImport
2 | 27a222ccc739de48d114ae282f848aad27775904 SubverionImport
3 | 0000000000000000000000000000000000000000 SubverionImport
4 | 27a222ccc739de48d114ae282f848aad27775904 SubversionImport
5 | 87ace7ce5663b6c662bcfa54818e099ffdcceb53 release_Yapsy-1.8
6 | 43a85ec50934b636dce6647eb5342b70b809ca20 release_Yapsy-1.9
7 | ee9987b833d65887487af38cecf0eb9ed6b871eb release_Yapsy-1.9-python3
8 | 991a7a83265127a463c845fa56ada2183cdaafe3 release_Yapsy-1.10
9 | 991a7a83265127a463c845fa56ada2183cdaafe3 release_Yapsy-1.10
10 | 0000000000000000000000000000000000000000 release_Yapsy-1.10
11 | 0000000000000000000000000000000000000000 release_Yapsy-1.10
12 | b934a474c2e8fa765bfda7bce134060184284872 release_Yapsy-1.10
13 | a5c62d9f560fa44bbf41fdd0d2c9ddc85144f637 release_Yapsy-1.10.1
14 | 7000b8072f00e42d9c448092bbd2149cc76e8d21 release_Yapsy-1.10-python3
15 | 0000000000000000000000000000000000000000 release_Yapsy-1.10-python3
16 | 0000000000000000000000000000000000000000 release_Yapsy-1.10-python3
17 | 87fbef4ba66ba9f98692e9b657479fe5daae12a2 release_Yapsy-1.10-python3
18 | f59fd5772939d6779725d0bb55b612ba5b254534 release_Yapsy-1.10.1-python3
19 | 5c0ff8646c2e1b6e0a4676b57676ed5adbe6b479 release_Yapsy-1.10.2
20 | c1f8228a9fd08bbffbfd8b6b8ecd1de5c2d9236f release_Yapsy-1.10.2-python3
21 | 95b58ee3f7f4cd225caf8ff84bf5808fbf52279a release_Yapsy-1.10.323
22 | 777d3daf4648d8395be90a2059c749bca191cbcd release_Yapsy-1.10.323-python3
23 | 9bc11370f4b364b4d7a977d900d850eb98b69a67 release_Yapsy-1.11.023
24 | 45f2588c6221d2ccf2b8937692be48af7e3ba54e release_Yapsy-1.10.423
25 | ce1bb3d8cb4d3bb3bac116cd0ba5165c624eaf53 release_Yapsy-1.11.223
26 | 2e9f7c40b53120a956fd612a8f454c7ffbd8b473 release_Yapsy-1.12.023
27 | cda16532447dfc74b0cac564fe099aaab71242a8 release_Yapsy-1.12.0
28 | 2e9f7c40b53120a956fd612a8f454c7ffbd8b473 release_Yapsy-1.12.023
29 | 0000000000000000000000000000000000000000 release_Yapsy-1.12.023
30 | cda16532447dfc74b0cac564fe099aaab71242a8 release_Yapsy-1.12.0
31 | 0000000000000000000000000000000000000000 release_Yapsy-1.12.0
32 | 0000000000000000000000000000000000000000 release_Yapsy-1.12.0
33 | c1566d5c5fb4f65c425b113dbe091990c7d4a18f release_Yapsy-1.12.0
34 | 851e4edcf10e37ea751b7e57980dbf26880f6418 release_Yapsy-1.12.2
35 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | builder: html
11 | configuration: package/doc/conf.py
12 |
13 | # Optionally build your docs in additional formats such as PDF and ePub
14 | formats: all
15 |
16 | # Optionally set the version of Python and requirements
17 | # required to build your docs
18 | python:
19 | version: 3
20 | install:
21 | - requirements: requirements-release.txt
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Yapsy
2 | =====
3 |
4 | A fat-free DIY Python plugin management toolkit.
5 |
6 | [](./package/LICENSE.txt)
7 | [](https://pypi.python.org/pypi/yapsy)
8 | [](https://yapsy.readthedocs.io/en/latest/?badge=latest)
9 | [](https://github.com/tibonihoo/yapsy/actions/workflows/automatic-tests.yml)
10 | [](https://coveralls.io/r/tibonihoo/yapsy?branch=master)
11 |
12 |
13 | Get more info on:
14 | * [the detailed description](./package/README.txt)
15 | * [the website](http://yapsy.sourceforge.net/)
16 | * [ReadTheDoc](https://yapsy.readthedocs.org)
17 |
18 |
19 | Yapsy's development is hosted on [github](https://github.com/tibonihoo/yapsy).
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/package/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | version-2.0.0 [????]
2 | - code: Python2 support is dropped, compat.py and its variables are gone.
3 |
4 | version-1.12.0 [2018-09-02]
5 | - code: fix yapsy on python3.6
6 | - code: Make the test more robust to "unusual" unpacking of the module (see: https://sourceforge.net/p/yapsy/bugs/32/)
7 | - code: Protect against providing a single string to setPluginPlaces (see: https://sourceforge.net/p/yapsy/bugs/38/)
8 | - code: Enforce the exact directory list provided at construction time (see: https://sourceforge.net/p/yapsy/bugs/36/)
9 | - code: Make multiprocess plugin work on windows too ! (see: https://sourceforge.net/p/yapsy/bugs/33/)
10 | - code: add a filter-based getter selecting plugins on plugininfo properties (see: https://sourceforge.net/p/yapsy/feature-requests/16/)
11 | - code: Add callback_after argument to the LoadPlugins method in PluginManager (contrib https://sourceforge.net/p/yapsy/feature-requests/9/)
12 | - code: Rejecting a candidate should not be a warning (contrib Guillaume Binet: https://github.com/tibonihoo/yapsy/pull/7)
13 | - code: fix PluginFileLocator __init__ should assignment of plugin_info_cls (contrib Xuecheng Zhang: https://github.com/tibonihoo/yapsy/pull/8)
14 |
15 | version-1.11.223 [2015-06-25]
16 | - doc: minor doc fixes
17 |
18 | version-1.11.123 [2015-05-08]
19 |
20 | - code: Make _extractCorePluginInfo accept Unicode filenames (bug https://sourceforge.net/p/yapsy/bugs/30/)
21 | - code: fix default change trigger for ConfigurablePluginManager (see https://sourceforge.net/p/yapsy/support-requests/9/)
22 |
23 | version-1.11.023 [2015-04-05]
24 |
25 | - code: merge python3 and default branch (contrib delijati)
26 | - code: fix exception catching to support flask use case (contrib delijati: https://github.com/tibonihoo/yapsy/pull/4)
27 | - code: fix error reporting (contrib frmdstryr: https://github.com/tibonihoo/yapsy/pull/5)
28 | - code: allow plugins to run in separate processes (contrib pylanglois: https://github.com/tibonihoo/yapsy/pull/6)
29 | - code: fix dangerous usage of mutable objects as default arguments
30 | - doc: added a few badges
31 | - doc: added an example of fetching yapsy's development version with pip
32 |
33 | version-1.10.423 [2014-06-07]
34 |
35 | - code: Speed optimisation for the regexp compiled in __init__.py (see https://sourceforge.net/p/yapsy/patches/4/)
36 | - code: fix bug "Plugin detection doesn't follow symlinks" (see https://sourceforge.net/p/yapsy/bugs/19/)
37 | - doc: add links to coveralls.io for code coverage
38 |
39 | version-1.10.323 [2014-03-23]
40 |
41 | - code: fix PluginInfo properties (see https://sourceforge.net/p/yapsy/bugs/13/)
42 | - code: fix ConfigurablePluginManager.loadplugin ignore callback bug reported at https://sourceforge.net/p/yapsy/bugs/17/
43 | - code: small improvement to the parse error handling (related to https://sourceforge.net/p/yapsy/bugs/12/)
44 |
45 | version-1.10.223 [2013-12-06]
46 |
47 | - packaging: version name change to comply with PEP440 and resolve pip install problems.
48 | - code: fix compatibility with python2.5
49 |
50 | version-1.10.2 [2013-05-22]
51 |
52 | - code: fix compatibility with python2.5
53 | - doc: add links to travis-ci and readthedocs.org
54 | - code: fix AutoInstall test failures [contrib. Agustin Henze]
55 | - code: replace deprecated methods usage (for Python3)
56 |
57 | version-1.10.1 [2013-01-13]
58 |
59 | - code: switch from exec to imp.load_module for plugin loading which also solves https://sourceforge.net/p/yapsy/bugs/9/
60 | - doc: add explanation about plugin class detection caveat https://sourceforge.net/p/yapsy/bugs/8/
61 | - code: fix unicode bug on python2 version, see https://sourceforge.net/p/yapsy/bugs/10/
62 |
63 | version-1.10 [2012-12-18]
64 |
65 | - code: [contrib. Mathieu Havel] "plugin locators" allow to change the strategy to describe and locate plugins
66 | - code: [contrib. Mathieu Clabaut] multiple categories per plugin (cf https://bitbucket.org/matclab/yapsy-mcl)
67 | - code: [contrib. Mark Fickett] improve logging
68 | - code: Gather detailed information on plugin load error via a callback
69 | - code: Extra info to plug-in (eg add extra section or embed the ConfigParser output to the plugin_info), see also https://github.com/tintinweb/yapsy
70 | - code: proper config of the default "plugin locator" can stop plugin detection from scanning a directory recursively
71 | - code: Enforce a same tab convention everywhere
72 | - doc: update list of project using yapsy
73 | - doc: highlight the existence of tutorial and link to these ones:
74 | - doc: be more helpful to users with an advice/troubleshooting page
75 | - doc: add a CHANGELOG.txt file
76 |
77 | version-1.9.2 [2012-07-15]
78 |
79 | - packaging fixes and strange version bumps to workaround pypi.python.org's version handling
80 |
81 | version-1.9 [2011-12-23]
82 |
83 | - ability to load zipped plugins
84 | - a separate development branch has been created where the focus is on the compatibility with python3
85 | - no more SVN repository (as advertised last year it wasn't kept in sync with the Mercurial repository, and it is now officially dead)
86 | - better logging of errors and debug infos
87 | - small doc improvement, especially to show how simple it is to interactwith the plugins once they are loaded
88 |
89 | version-1.8 [2010-09-26]
90 |
91 | - the documentation has been refactored and should now go "straight to the point"
92 | - the source control is now performed by Mercurial
93 | - Filtering manager to filter out plugins that must not be loaded, contributed by Roger Gammans
94 | - a getAllPlugins method has been added to the PluginManager to make it easier to access plugins when only the default category is defined
95 | - code has been slightly cleaned up and should now be easy to adapt to Python3 via the 2to3 tool.
96 |
97 | version-1.7 [2008-04-09]
98 |
99 | - WARNING: API BREAK ! the arguments for [de]activatePluginByName and getPluginByName are now the other way round: category,name -> name,category="Default"
100 | - new AutoInstall manager for automatically installing plugins by copying them in proper place
101 | - small improvements to generic code for plugin loading
102 |
103 | version-1.6 [2007-11-10]
104 |
105 | - fix major bug in ConfigurablePluginManager
106 |
107 | version-1.5 [2007-11-03]
108 |
109 | - separation of plugin loading into locate and load contributed by Rob McMullen
110 | - package with "Easy install" framework
111 | - new forge (https://sourceforge.net/p/yapsy) and independent repo from mathbench
112 |
113 | version-1.1 [2007-09-21]
114 |
115 | - VersionedPlugin manager contributed by Rob McMullen
116 |
117 | version-1.0 [2007-08-26]
118 |
119 | - basic implementation of a PluginManager
120 | - ConfigurablePlugin manager that can store information in a ConfigParser compatible file
121 | - singleton versions of these plugin managers.
122 |
--------------------------------------------------------------------------------
/package/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Yapsy is provided under the BSD-2 clause license (see text below),
2 | with the following two exceptions:
3 |
4 | - the "yapsy" icons in artwork/ is licensed under the Creative Commons
5 | Attribution-Share Alike 3.0 by Thibauld Nion (see
6 | artwork/LICENSE.txt)
7 |
8 |
9 | --------------------
10 | BSD 2-clause license
11 | --------------------
12 |
13 | Copyright (c) 2007-2015, Thibauld Nion
14 |
15 | All rights reserved.
16 |
17 |
18 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
19 |
20 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
21 | * 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.
22 |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 |
--------------------------------------------------------------------------------
/package/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.txt
2 | include LICENSE.txt
3 | include CHANGELOG.txt
4 | include runtests.py
5 |
6 | recursive-include test *.py *-plugin *.zip
7 | recursive-include yapsy *.py
8 |
9 | recursive-include artwork *
10 | recursive-include doc *
11 | prune doc/_build
12 |
--------------------------------------------------------------------------------
/package/README.txt:
--------------------------------------------------------------------------------
1 | Yapsy is a small library implementing the core mechanisms needed to
2 | build a plugin system into a wider application.
3 |
4 | The main purpose is to depend only on Python's standard libraries and
5 | to implement only the basic functionalities needed to detect, load and
6 | keep track of several plugins. It supports both Python 2 and 3.
7 |
8 | To use yapsy, make sure that the "yapsy" directory is in your Python
9 | loading path and just import the needed class from yapsy (e.g. "from
10 | yapsy.PluginManager import PluginManager").
11 |
12 | To see more examples, you can have a look at the unit tests inside the
13 | "test" directory or at the "Showcase and tutorials" section of the
14 | documentation (https://yapsy.readthedocs.io/en/latest/#showcase-and-tutorials).
15 |
16 | Please let me know if you find this useful.
17 |
18 | Site of the project: https://yapsy.readthedocs.io
19 |
20 | List of Contributors:
21 | - Thibauld Nion
22 | - Rob McMullen
23 | - Roger Gammans
24 | - Mathieu Havel
25 | - Mathieu Clabaut
26 | - Mark Fickett
27 | - Agustin Henze
28 | - qitta
29 | - Roberto Alsina
30 | - Josip Delic (delijati)
31 | - frmdstryr
32 | - Pierre-Yves Langlois
33 | - Guillaume Binet (gbin)
34 | - Blake Oliver (Oliver2213)
35 | - Xuecheng Zhang (csuzhangxc)
36 | - Marc Brooks (mbrooks-public)
37 | - Ameya Vikram Singh (AmeyaVS)
38 |
39 | Contributions are welcome as pull requests, patches or tickets on github
40 | (https://github.com/tibonihoo/yapsy).
41 |
--------------------------------------------------------------------------------
/package/TODO.txt:
--------------------------------------------------------------------------------
1 | ======
2 | TODO
3 | ======
4 |
5 | Next Release
6 |
7 |
8 | Later
9 |
10 |
11 |
12 | Next Refactoring
13 |
14 | - code: consider making the filter and versionned plugin into plugin manager child classes (instead of decorators), err or maybe strategies ?
15 | - code: find a correct design to make extending the plugin "loading" easier and chainable (policies/mixins, traits ?)
16 | - code: [feature req.] Reloadable configuration for plugins
17 | - code: [feature req.] Allow Decorators to extend gatherPluginInfo
18 |
--------------------------------------------------------------------------------
/package/artwork/yapsy-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/artwork/yapsy-big.png
--------------------------------------------------------------------------------
/package/artwork/yapsy-favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/artwork/yapsy-favicon.ico
--------------------------------------------------------------------------------
/package/artwork/yapsy-favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/artwork/yapsy-favicon.png
--------------------------------------------------------------------------------
/package/artwork/yapsy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/artwork/yapsy.png
--------------------------------------------------------------------------------
/package/artwork/yapsy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
149 |
--------------------------------------------------------------------------------
/package/doc/Advices.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | General advices and troubleshooting
3 | ===================================
4 |
5 | .. contents::
6 | :local:
7 |
8 |
9 | Getting code samples
10 | --------------------
11 |
12 | Yapsy is used enough for your favorite search provider to have good
13 | chances of finding some examples of yapsy being used in the wild.
14 |
15 | However if you wonder how a specific functionality can be used, you
16 | can also look at the corresponding unit test (in the test folder
17 | packaged with yapsy's sources).
18 |
19 |
20 | Use the logging system
21 | ----------------------
22 |
23 | Yapsy uses Python's standard ``logging`` module to record most
24 | important events and especially plugin loading failures.
25 |
26 | When developping an application based on yapsy, you'll benefit from
27 | looking at the 'debug' level logs, which can easily be done from your
28 | application code with the following snippet::
29 |
30 | import logging
31 | logging.basicConfig(level=logging.DEBUG)
32 |
33 | Also, please note that yapsy uses a named logger for all its logs, so
34 | that you can selectively activage debug logs for yapsy with the
35 | following snippet::
36 |
37 | import logging
38 | logging.getLogger('yapsy').setLevel(logging.DEBUG)
39 |
40 |
41 | Categorization by inheritance caveat
42 | ------------------------------------
43 |
44 | If your application defines various categories of plugins with the yapsy's built-in mechanism for that, please keep in mind the following facts:
45 |
46 | - a plugin instance is attributed to a given category by looking if
47 | it is an instance, *even via a subclass*, of the class associated
48 | to this category;
49 | - a plugin may be attributed to several categories.
50 |
51 | Considering this, and if you consider using several categories, you
52 | should consider the following tips:
53 |
54 | - **don't associate any category to ``IPlugin``** (unless you want
55 | all plugins to be attributed to the corresponding category)
56 | - **design a specific subclass** of ``IPlugin`` for each category
57 | - if you want to regroup plugins of some categories into a common
58 | category: do this by attributing a subclass of ``IPlugin`` to the
59 | common category and attribute to the other categories specific
60 | subclasses to this intermediate mother class so that **the plugin
61 | class inheritance hierarchy reflects the hierarchy between
62 | categories** (and if you want something more complex that a
63 | hierarchy, you can consider using mixins).
64 |
65 |
66 | Plugin class detection caveat
67 | -----------------------------
68 |
69 | There must be **only one plugin defined per module**. This means that
70 | you can't have two plugin description files pointing at the same
71 | module for instance.
72 |
73 | Because of the "categorization by inheritance" system, you **musn't
74 | directly import the subclass** of ``IPlugin`` in the main plugin file,
75 | instead import its containing module and make your plugin class
76 | inherit from ``ContainingModule.SpecificPluginClass`` as in the
77 | following example.
78 |
79 | The following code won't work (the class ``MyBasePluginClass`` will be
80 | detected as the plugin's implementation instead of ``MyPlugin``)::
81 |
82 | from myapp.plugintypes import MyBasePluginClass
83 |
84 | class MyPlugin(MyBasePluginClass):
85 | pass
86 |
87 | Instead you should do the following::
88 |
89 | import myapp.plugintypes as plugintypes
90 |
91 | class MyPlugin(plugintypes.MyBasePluginClass):
92 | pass
93 |
94 |
95 | Plugin packaging
96 | ----------------
97 |
98 | When packaging plugins in a distutils installer or as parts of an
99 | application (like for instance with `py2exe`), you may want to take
100 | care about the following points:
101 |
102 | - when you set specific directories where to look for plugins with a
103 | hardcoded path, be very carefully about the way you write these
104 | paths because depending on the cases **using ``__file__`` or
105 | relative paths may be unreliable**. For instance with py2exe, you
106 | may want to follow the tips from the `Where Am I FAQ`_.
107 |
108 | - you'd should either **package the plugins as plain Python modules or
109 | data files** (if you want to consider you application as the only
110 | module), either using the dedicated `setup` argument for `py2exe` or
111 | using distutils' `MANIFEST.in`
112 |
113 | - if you do package the plugins as data files, **make sure that their
114 | dependencies are correctly indicated as dependencies of your
115 | package** (or packaged with you application if you use `py2exe`).
116 |
117 | See also a more detailed example for py2exe on `Simon on Tech's Using python plugin scripts with py2exe`_.
118 |
119 | .. _`Where Am I FAQ`: http://www.py2exe.org/index.cgi/WhereAmI
120 | .. _`Simon on Tech's Using python plugin scripts with py2exe`: http://notinthestars.blogspot.com.es/2011/04/using-python-plugin-scripts-with-py2exe.html
121 |
122 |
123 | Code conventions
124 | ----------------
125 |
126 | If you intend to modify yapsy's sources and to contribute patches
127 | back, please respect the following conventions:
128 |
129 | - CamelCase (upper camel case) for class names and functions
130 | - camelCase (lower camel case) for methods
131 | - UPPERCASE for global variables (with a few exceptions)
132 | - tabulations are used for indentation (and not spaces !)
133 | - unit-test each new functionality
134 |
135 |
--------------------------------------------------------------------------------
/package/doc/AutoInstallPluginManager.rst:
--------------------------------------------------------------------------------
1 | AutoInstallPluginManager
2 | ========================
3 |
4 | .. automodule:: yapsy.AutoInstallPluginManager
5 | :members:
6 | :undoc-members:
7 |
8 |
--------------------------------------------------------------------------------
/package/doc/ConfigurablePluginManager.rst:
--------------------------------------------------------------------------------
1 | ConfigurablePluginManager
2 | =========================
3 |
4 | .. automodule:: yapsy.ConfigurablePluginManager
5 | :members:
6 | :undoc-members:
7 |
8 |
--------------------------------------------------------------------------------
/package/doc/Extensions.rst:
--------------------------------------------------------------------------------
1 | ===================
2 | Built-in Extensions
3 | ===================
4 |
5 | The followig ready-to-use classes give you this exact extra
6 | functionality you need for your plugin manager:
7 |
8 |
9 | .. toctree::
10 | :maxdepth: 1
11 |
12 | VersionedPluginManager
13 | ConfigurablePluginManager
14 | AutoInstallPluginManager
15 | FilteredPluginManager
16 | MultiprocessPluginManager
17 |
18 |
19 | The following item offer customization for the way plugins are
20 | described and detected:
21 |
22 | .. toctree::
23 | :maxdepth: 1
24 |
25 | PluginFileLocator
26 |
27 |
28 | If you want to build your own extensions, have a look at the following
29 | interfaces:
30 |
31 | .. toctree::
32 | :maxdepth: 1
33 |
34 | IPluginLocator
35 | PluginManagerDecorator
36 |
37 | If you want to isolate your plugins in separate processes with the
38 | ``MultiprocessPluginManager``, you should look at the following
39 | classes too:
40 |
41 | .. toctree::
42 | :maxdepth: 1
43 |
44 | IMultiprocessChildPlugin
45 | MultiprocessPluginProxy
46 |
--------------------------------------------------------------------------------
/package/doc/FilteredPluginManager.rst:
--------------------------------------------------------------------------------
1 | FilteredPluginManager
2 | =====================
3 |
4 | .. automodule:: yapsy.FilteredPluginManager
5 | :members:
6 | :undoc-members:
7 |
8 |
--------------------------------------------------------------------------------
/package/doc/IMultiprocessChildPlugin.rst:
--------------------------------------------------------------------------------
1 | ========================
2 | IMultiprocessChildPlugin
3 | ========================
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | .. automodule:: yapsy.IMultiprocessChildPlugin
9 | :members:
10 | :undoc-members:
11 |
--------------------------------------------------------------------------------
/package/doc/IPlugin.rst:
--------------------------------------------------------------------------------
1 | IPlugin
2 | =======
3 |
4 | .. automodule:: yapsy.IPlugin
5 | :members:
6 | :undoc-members:
7 |
8 |
--------------------------------------------------------------------------------
/package/doc/IPluginLocator.rst:
--------------------------------------------------------------------------------
1 | IPluginLocator
2 | ==============
3 |
4 | .. automodule:: yapsy.IPluginLocator
5 | :members:
6 | :undoc-members:
7 |
--------------------------------------------------------------------------------
/package/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 |
9 | # Internal variables.
10 | PAPEROPT_a4 = -D latex_paper_size=a4
11 | PAPEROPT_letter = -D latex_paper_size=letter
12 | ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
13 |
14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
15 |
16 | help:
17 | @echo "Please use \`make ' where is one of"
18 | @echo " html to make standalone HTML files"
19 | @echo " dirhtml to make HTML files named index.html in directories"
20 | @echo " pickle to make pickle files"
21 | @echo " json to make JSON files"
22 | @echo " htmlhelp to make HTML files and a HTML help project"
23 | @echo " qthelp to make HTML files and a qthelp project"
24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
25 | @echo " changes to make an overview of all changed/added/deprecated items"
26 | @echo " linkcheck to check all external links for integrity"
27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
28 |
29 | clean:
30 | -rm -rf _build/*
31 |
32 | html:
33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
34 | @echo
35 | @echo "Build finished. The HTML pages are in _build/html."
36 |
37 | dirhtml:
38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
39 | @echo
40 | @echo "Build finished. The HTML pages are in _build/dirhtml."
41 |
42 | pickle:
43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
44 | @echo
45 | @echo "Build finished; now you can process the pickle files."
46 |
47 | json:
48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
49 | @echo
50 | @echo "Build finished; now you can process the JSON files."
51 |
52 | htmlhelp:
53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
54 | @echo
55 | @echo "Build finished; now you can run HTML Help Workshop with the" \
56 | ".hhp project file in _build/htmlhelp."
57 |
58 | qthelp:
59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
60 | @echo
61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
62 | ".qhcp project file in _build/qthelp, like this:"
63 | @echo "# qcollectiongenerator _build/qthelp/Yapsy.qhcp"
64 | @echo "To view the help file:"
65 | @echo "# assistant -collectionFile _build/qthelp/Yapsy.qhc"
66 |
67 | latex:
68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
69 | @echo
70 | @echo "Build finished; the LaTeX files are in _build/latex."
71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
72 | "run these through (pdf)latex."
73 |
74 | changes:
75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
76 | @echo
77 | @echo "The overview file is in _build/changes."
78 |
79 | linkcheck:
80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
81 | @echo
82 | @echo "Link check complete; look for any errors in the above output " \
83 | "or in _build/linkcheck/output.txt."
84 |
85 | doctest:
86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
87 | @echo "Testing of doctests in the sources finished, look at the " \
88 | "results in _build/doctest/output.txt."
89 |
--------------------------------------------------------------------------------
/package/doc/MultiprocessPluginManager.rst:
--------------------------------------------------------------------------------
1 | =========================
2 | MultiprocessPluginManager
3 | =========================
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 |
9 | .. automodule:: yapsy.MultiprocessPluginManager
10 | :members:
11 | :undoc-members:
12 |
13 |
--------------------------------------------------------------------------------
/package/doc/MultiprocessPluginProxy.rst:
--------------------------------------------------------------------------------
1 | =======================
2 | MultiprocessPluginProxy
3 | =======================
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | .. automodule:: yapsy.MultiprocessPluginProxy
9 | :members:
10 | :undoc-members:
11 |
--------------------------------------------------------------------------------
/package/doc/PluginFileLocator.rst:
--------------------------------------------------------------------------------
1 | PluginFileLocator
2 | =================
3 |
4 | .. automodule:: yapsy.PluginFileLocator
5 | :members:
6 | :undoc-members:
7 |
--------------------------------------------------------------------------------
/package/doc/PluginInfo.rst:
--------------------------------------------------------------------------------
1 | ==========
2 | PluginInfo
3 | ==========
4 |
5 | .. automodule:: yapsy.PluginInfo
6 | :members:
7 | :undoc-members:
8 |
9 |
--------------------------------------------------------------------------------
/package/doc/PluginManager.rst:
--------------------------------------------------------------------------------
1 | =============
2 | PluginManager
3 | =============
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 |
9 | .. automodule:: yapsy.PluginManager
10 | :members:
11 | :undoc-members:
12 |
13 |
--------------------------------------------------------------------------------
/package/doc/PluginManagerDecorator.rst:
--------------------------------------------------------------------------------
1 | ======================
2 | PluginManagerDecorator
3 | ======================
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 |
9 | .. automodule:: yapsy.PluginManagerDecorator
10 | :members:
11 | :undoc-members:
12 |
13 |
--------------------------------------------------------------------------------
/package/doc/VersionedPluginManager.rst:
--------------------------------------------------------------------------------
1 | VersionedPluginManager
2 | ======================
3 |
4 | .. automodule:: yapsy.VersionedPluginManager
5 | :members:
6 | :undoc-members:
7 |
8 |
--------------------------------------------------------------------------------
/package/doc/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Yapsy documentation build configuration file, created by
4 | # sphinx-quickstart on Sat Aug 21 19:38:34 2010.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | SRC_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
17 | sys.path = [SRC_DIR] + sys.path
18 |
19 | # If extensions (or modules to document with autodoc) are in another directory,
20 | # add these directories to sys.path here. If the directory is relative to the
21 | # documentation root, use os.path.abspath to make it absolute, like shown here.
22 | #sys.path.append(os.path.abspath('.'))
23 |
24 | # -- General configuration -----------------------------------------------------
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage']
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = 'Yapsy'
44 | copyright = '2007-2018, Thibauld Nion'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 |
51 | import sys
52 | sys.path.insert(0,os.path.dirname(__file__))
53 | import yapsy
54 | # The short X.Y version.
55 | version = yapsy.__version__
56 | # The full version, including alpha/beta/rc tags.
57 | release = yapsy.__version__
58 |
59 | # The language for content autogenerated by Sphinx. Refer to documentation
60 | # for a list of supported languages.
61 | #language = None
62 |
63 | # There are two options for replacing |today|: either, you set today to some
64 | # non-false value, then it is used:
65 | #today = ''
66 | # Else, today_fmt is used as the format for a strftime call.
67 | #today_fmt = '%B %d, %Y'
68 |
69 | # List of documents that shouldn't be included in the build.
70 | #unused_docs = []
71 |
72 | # List of directories, relative to source directory, that shouldn't be searched
73 | # for source files.
74 | exclude_trees = ['_build']
75 |
76 | # The reST default role (used for this markup: `text`) to use for all documents.
77 | #default_role = None
78 |
79 | # If true, '()' will be appended to :func: etc. cross-reference text.
80 | #add_function_parentheses = True
81 |
82 | # If true, the current module name will be prepended to all description
83 | # unit titles (such as .. function::).
84 | #add_module_names = True
85 |
86 | # If true, sectionauthor and moduleauthor directives will be shown in the
87 | # output. They are ignored by default.
88 | #show_authors = False
89 |
90 | # The name of the Pygments (syntax highlighting) style to use.
91 | pygments_style = 'sphinx'
92 |
93 | # A list of ignored prefixes for module index sorting.
94 | #modindex_common_prefix = []
95 |
96 |
97 | # -- Options for HTML output ---------------------------------------------------
98 |
99 | # The theme to use for HTML and HTML Help pages. Major themes that come with
100 | # Sphinx are currently 'default' and 'sphinxdoc'.
101 | html_theme = 'default'
102 |
103 | # Theme options are theme-specific and customize the look and feel of a theme
104 | # further. For a list of options available for each theme, see the
105 | # documentation.
106 | html_theme_options = {
107 | "sidebarbgcolor" : "#777",
108 | "sidebarlinkcolor": "#e0cede",
109 | "relbarbgcolor" : "#999",
110 | "relbarlinkcolor": "#e0cede",
111 | "footerbgcolor" : "#777",
112 | "headtextcolor" : "#5c3566",
113 | "linkcolor": "#5c3566",
114 | }
115 |
116 | # Add any paths that contain custom themes here, relative to this directory.
117 | #html_theme_path = []
118 |
119 | # The name for this set of Sphinx documents. If None, it defaults to
120 | # " v documentation".
121 | #html_title = None
122 |
123 | # A shorter title for the navigation bar. Default is the same as html_title.
124 | #html_short_title = None
125 |
126 | # The name of an image file (relative to this directory) to place at the top
127 | # of the sidebar.
128 | html_logo = os.path.join(SRC_DIR,"artwork","yapsy-big.png")
129 |
130 | # The name of an image file (within the static path) to use as favicon of the
131 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
132 | # pixels large.
133 | html_favicon = os.path.join(SRC_DIR,"artwork","yapsy-favicon.ico")
134 |
135 | # Add any paths that contain custom static files (such as style sheets) here,
136 | # relative to this directory. They are copied after the builtin static files,
137 | # so a file named "default.css" will overwrite the builtin "default.css".
138 | html_static_path = []
139 |
140 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
141 | # using the given strftime format.
142 | html_last_updated_fmt = '%b %d, %Y'
143 |
144 | # If true, SmartyPants will be used to convert quotes and dashes to
145 | # typographically correct entities.
146 | #html_use_smartypants = True
147 |
148 | # Custom sidebar templates, maps document names to template names.
149 | #html_sidebars = {}
150 |
151 | # Additional templates that should be rendered to pages, maps page names to
152 | # template names.
153 | #html_additional_pages = {}
154 |
155 | # If false, no module index is generated.
156 | #html_use_modindex = True
157 |
158 | # If false, no index is generated.
159 | #html_use_index = True
160 |
161 | # If true, the index is split into individual pages for each letter.
162 | #html_split_index = False
163 |
164 | # If true, links to the reST sources are added to the pages.
165 | #html_show_sourcelink = True
166 |
167 | # If true, an OpenSearch description file will be output, and all pages will
168 | # contain a tag referring to it. The value of this option must be the
169 | # base URL from which the finished HTML is served.
170 | #html_use_opensearch = ''
171 |
172 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
173 | #html_file_suffix = ''
174 |
175 | # Output file base name for HTML help builder.
176 | htmlhelp_basename = 'Yapsydoc'
177 |
178 |
179 | # -- Options for LaTeX output --------------------------------------------------
180 |
181 | # The paper size ('letter' or 'a4').
182 | #latex_paper_size = 'letter'
183 |
184 | # The font size ('10pt', '11pt' or '12pt').
185 | #latex_font_size = '10pt'
186 |
187 | # Grouping the document tree into LaTeX files. List of tuples
188 | # (source start file, target name, title, author, documentclass [howto/manual]).
189 | latex_documents = [
190 | ('index', 'Yapsy.tex', 'Yapsy Documentation',
191 | 'Thibauld Nion', 'manual'),
192 | ]
193 |
194 | # The name of an image file (relative to this directory) to place at the top of
195 | # the title page.
196 | #latex_logo = None
197 |
198 | # For "manual" documents, if this is true, then toplevel headings are parts,
199 | # not chapters.
200 | #latex_use_parts = False
201 |
202 | # Additional stuff for the LaTeX preamble.
203 | #latex_preamble = ''
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_use_modindex = True
210 |
--------------------------------------------------------------------------------
/package/doc/index.rst:
--------------------------------------------------------------------------------
1 | .. Yapsy documentation master file, created by
2 | sphinx-quickstart on Sat Aug 21 19:38:34 2010.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | ================================
7 | Yapsy: Yet Another Plugin SYstem
8 | ================================
9 |
10 | *A simple plugin system for Python applications*
11 |
12 |
13 | .. |Yapsy| replace:: **Yapsy**
14 | .. |CC-BYSA| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png
15 | :alt: Creative Commons License
16 |
17 |
18 | Quick links:
19 |
20 | .. toctree::
21 | :maxdepth: 1
22 |
23 | IPlugin
24 | PluginManager
25 | PluginInfo
26 | Extensions
27 | Advices
28 |
29 |
30 | .. contents:: On this page
31 | :local:
32 |
33 |
34 | .. automodule:: yapsy
35 | :members:
36 | :undoc-members:
37 |
38 | .. _extend:
39 |
40 | Make it your own
41 | ================
42 |
43 | For applications that require the plugins and their managers to be
44 | more sophisticated, several techniques make such enhancement easy. The
45 | following sections detail the most frequent needs for extensions
46 | and what you can do about it.
47 |
48 |
49 | More sophisticated plugin classes
50 | ---------------------------------
51 |
52 | You can define a plugin class with a richer interface than
53 | ``IPlugin``, so long as it inherits from IPlugin, it should work the
54 | same. The only thing you need to know is that the plugin instance is
55 | accessible via the ``PluginInfo`` instance from its
56 | ``PluginInfo.plugin_object``.
57 |
58 |
59 | It is also possible to define a wider variety of plugins, by defining
60 | as much subclasses of IPlugin. But in such a case you have to inform
61 | the manager about that before collecting plugins::
62 |
63 | # Build the manager
64 | simplePluginManager = PluginManager()
65 | # Tell it the default place(s) where to find plugins
66 | simplePluginManager.setPluginPlaces(["path/to/myplugins"])
67 | # Define the various categories corresponding to the different
68 | # kinds of plugins you have defined
69 | simplePluginManager.setCategoriesFilter({
70 | "Playback" : IPlaybackPlugin,
71 | "SongInfo" : ISongInfoPlugin,
72 | "Visualization" : IVisualisation,
73 | })
74 |
75 |
76 | .. note:: Communicating with the plugins belonging to a given category
77 | might then be achieved with some code looking like the
78 | following::
79 |
80 | # Trigger 'some action' from the "Visualization" plugins
81 | for pluginInfo in simplePluginManager.getPluginsOfCategory("Visualization"):
82 | pluginInfo.plugin_object.doSomething(...)
83 |
84 |
85 | Enhance the plugin manager's interface
86 | --------------------------------------
87 |
88 | To make the plugin manager more helpful to the other components of an
89 | application, you should consider decorating it.
90 |
91 | Actually a "template" for such decoration is provided as
92 | :doc:`PluginManagerDecorator`, which must be inherited in order to
93 | implement the right decorator for your application.
94 |
95 | Such decorators can be chained, so that you can take advantage of the ready-made decorators such as:
96 |
97 | :doc:`ConfigurablePluginManager`
98 |
99 | Implements a ``PluginManager`` that uses a configuration file to
100 | save the plugins to be activated by default and also grants access
101 | to this file to the plugins.
102 |
103 |
104 | :doc:`AutoInstallPluginManager`
105 |
106 | Automatically copy the plugin files to the right plugin directory.
107 |
108 | A full list of pre-implemented decorators is available at :doc:`Extensions`.
109 |
110 |
111 | Modify plugin descriptions and detections
112 | -----------------------------------------
113 |
114 | By default, plugins are described by a text file called the plugin
115 | "info file" expected to have a ".yapsy-plugin" extension.
116 |
117 | You may want to use another way to describe and detect your
118 | application's plugin and happily yapsy (since version 1.10) makes it
119 | possible to provide the ``PluginManager`` with a custom strategy for
120 | plugin detection.
121 |
122 | See :doc:`IPluginLocator` for the required interface of such
123 | strategies and :doc:`PluginFileLocator` for a working example of such
124 | a detection strategy.
125 |
126 |
127 | Modify the way plugins are loaded
128 | ---------------------------------
129 |
130 | To tweak the plugin loading phase it is highly advised to re-implement
131 | your own manager class.
132 |
133 | The nice thing is, if your new manager inherits ``PluginManager``, then it will naturally fit as the start point of any decoration chain. You just have to provide an instance of this new manager to the first decorators, like in the following::
134 |
135 | # build and configure a specific manager
136 | baseManager = MyNewManager()
137 | # start decorating this manager to add some more responsibilities
138 | myFirstDecorator = AFirstPluginManagerDecorator(baseManager)
139 | # add even more stuff
140 | mySecondDecorator = ASecondPluginManagerDecorator(myFirstDecorator)
141 |
142 | .. note:: Some decorators have been implemented that modify the way
143 | plugins are loaded, this is however not the easiest way to
144 | do it and it makes it harder to build a chain of decoration
145 | that would include these decorators. Among those are
146 | :doc:`VersionedPluginManager` and
147 | :doc:`FilteredPluginManager`
148 |
149 |
150 | Showcase and tutorials
151 | ======================
152 |
153 | |yapsy| 's development has been originally motivated by the MathBench_
154 | project but it is now used in other (more advanced) projects like:
155 |
156 | - peppy_ : "an XEmacs-like editor in Python. Eventually. "
157 | - MysteryMachine_ : "an application for writing freeform games."
158 | - Aranduka_ : "A simple e-book manager and reader"
159 | - err_ : "a plugin based chatbot"
160 | - nikola_ : "a Static Site and Blog Generator"
161 |
162 | .. _MathBench: http://mathbench.sourceforge.net
163 | .. _peppy: http://www.flipturn.org/peppy/
164 | .. _MysteryMachine: http://trac.backslashat.org/MysteryMachine
165 | .. _Aranduka: https://github.com/ralsina/aranduka
166 | .. _err: http://gbin.github.com/err/
167 | .. _nikola: http://nikola.ralsina.com.ar/
168 |
169 | Nowadays, the development is clearly motivated by such external projects and the enthusiast developpers who use the library.
170 |
171 | If you're interested in using yapsy, feel free to look into the following links:
172 |
173 | - :doc:`Advices`
174 | - `A minimal example on stackoverflow`_
175 | - `Making your app modular: Yapsy`_ (applied to Qt apps)
176 | - `Python plugins with yapsy`_ (applied to GTK apps)
177 |
178 | .. _`Making your app modular: Yapsy`: http://ralsina.me/weblog/posts/BB923.html
179 | .. _`A minimal example on stackoverflow`: http://stackoverflow.com/questions/5333128/yapsy-minimal-example
180 | .. _`Python plugins with yapsy`: https://github.com/MicahCarrick/yapsy-gtk-example
181 |
182 |
183 | Development
184 | ===========
185 |
186 |
187 | Contributing or forking ?
188 | -------------------------
189 |
190 | You're always welcome if you suggest any kind of enhancements, any new
191 | decorators or any new pluginmanager. Even more if there is some code
192 | coming with it though this is absolutely not compulsory.
193 |
194 | It is also really fine to *fork* the code ! In the past, some people
195 | found |yapsy| just good enough to be used as a "code base" for their
196 | own plugin system, which they evolved in a more or less incompatible
197 | way with the "original" |yapsy|, if you think about it, with such a
198 | small library this is actually a clever thing to do.
199 |
200 | In any case, please remember that just providing some feedback on where
201 | you're using |yapsy| (original or forked) and how it is useful to you,
202 | is in itself a appreciable contribution :)
203 |
204 |
205 | License
206 | -------
207 |
208 | The work is placed under the simplified BSD_ license in order to make
209 | it as easy as possible to be reused in other projects.
210 |
211 | .. _BSD: http://www.opensource.org/licenses/bsd-license.php
212 |
213 | Please note that the icon is not under the same license but under the
214 | `Creative Common Attribution-ShareAlike`_ license.
215 |
216 | .. _`Creative Common Attribution-ShareAlike`: http://creativecommons.org/licenses/by-sa/3.0/
217 |
218 |
219 | Forge
220 | -----
221 |
222 | The project is hosted on `GitHub`_.
223 |
224 | * To use `pip for a development install`_ you can do something like::
225 |
226 | pip install -e "git+https://github.com/tibonihoo/yapsy.git#egg=yapsy&subdirectory=package"
227 |
228 | * A development version of the documentation is available on `ReadTheDoc`_.
229 |
230 |
231 | .. _`GitHub`: https://github.com/tibonihoo/yapsy/
232 | .. _`pip for a development install`: http://pip.readthedocs.org/en/latest/reference/pip_install.html#vcs-support
233 | .. _`ReadTheDoc`: https://yapsy.readthedocs.org
234 |
235 |
236 |
237 | References
238 | ----------
239 |
240 | Other Python plugin systems already existed before |yapsy| and some
241 | have appeared after that. |yapsy|'s creation is by no mean a sign that
242 | these others plugin systems sucks :) It is just the results of me
243 | being slighlty lazy and as I had already a good idea of how a simple
244 | plugin system should look like, I wanted to implement my own
245 | [#older_systems]_.
246 |
247 |
248 | - setuptools_ seems to be designed to allow applications to have a
249 | plugin system.
250 |
251 | .. _setuptools: http://cheeseshop.python.org/pypi/setuptools
252 |
253 |
254 | - Sprinkles_ seems to be also quite lightweight and simple but just
255 | maybe too far away from the design I had in mind.
256 |
257 | .. _Sprinkles: http://termie.pbwiki.com/SprinklesPy
258 |
259 |
260 | - PlugBoard_ is certainly quite good also but too complex for me. It also
261 | depends on zope which considered what I want to do here is way too
262 | much.
263 |
264 | .. _PlugBoard: https://pypi.python.org/pypi/PlugBoard
265 |
266 | - `Marty Alchin's simple plugin framework`_ is a quite interesting
267 | description of a plugin architecture with code snippets as
268 | illustrations.
269 |
270 | .. _`Marty Alchin's simple plugin framework`: http://martyalchin.com/2008/jan/10/simple-plugin-framework/
271 |
272 | - stevedor_ looks quite promising and actually seems to make
273 | setuptools relevant to build plugin systems.
274 |
275 | .. _stevedor: https://pypi.python.org/pypi/stevedore
276 |
277 | - You can look up more example on a `stackoverflow's discution about minimal plugin systems in Python`_
278 |
279 | .. _`stackoverflow's discution about minimal plugin systems in Python`: http://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python
280 |
281 |
282 | .. [#older_systems] All the more because it seems that my modest
283 | design ideas slightly differ from what has been done in other
284 | libraries.
285 |
286 | Indices and tables
287 | ==================
288 |
289 | * :ref:`genindex`
290 | * :ref:`modindex`
291 | * :ref:`search`
292 |
293 |
--------------------------------------------------------------------------------
/package/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | set SPHINXBUILD=sphinx-build
6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% .
7 | if NOT "%PAPER%" == "" (
8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
9 | )
10 |
11 | if "%1" == "" goto help
12 |
13 | if "%1" == "help" (
14 | :help
15 | echo.Please use `make ^` where ^ is one of
16 | echo. html to make standalone HTML files
17 | echo. dirhtml to make HTML files named index.html in directories
18 | echo. pickle to make pickle files
19 | echo. json to make JSON files
20 | echo. htmlhelp to make HTML files and a HTML help project
21 | echo. qthelp to make HTML files and a qthelp project
22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
23 | echo. changes to make an overview over all changed/added/deprecated items
24 | echo. linkcheck to check all external links for integrity
25 | echo. doctest to run all doctests embedded in the documentation if enabled
26 | goto end
27 | )
28 |
29 | if "%1" == "clean" (
30 | for /d %%i in (_build\*) do rmdir /q /s %%i
31 | del /q /s _build\*
32 | goto end
33 | )
34 |
35 | if "%1" == "html" (
36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html
37 | echo.
38 | echo.Build finished. The HTML pages are in _build/html.
39 | goto end
40 | )
41 |
42 | if "%1" == "dirhtml" (
43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml
44 | echo.
45 | echo.Build finished. The HTML pages are in _build/dirhtml.
46 | goto end
47 | )
48 |
49 | if "%1" == "pickle" (
50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle
51 | echo.
52 | echo.Build finished; now you can process the pickle files.
53 | goto end
54 | )
55 |
56 | if "%1" == "json" (
57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json
58 | echo.
59 | echo.Build finished; now you can process the JSON files.
60 | goto end
61 | )
62 |
63 | if "%1" == "htmlhelp" (
64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp
65 | echo.
66 | echo.Build finished; now you can run HTML Help Workshop with the ^
67 | .hhp project file in _build/htmlhelp.
68 | goto end
69 | )
70 |
71 | if "%1" == "qthelp" (
72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp
73 | echo.
74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
75 | .qhcp project file in _build/qthelp, like this:
76 | echo.^> qcollectiongenerator _build\qthelp\Yapsy.qhcp
77 | echo.To view the help file:
78 | echo.^> assistant -collectionFile _build\qthelp\Yapsy.ghc
79 | goto end
80 | )
81 |
82 | if "%1" == "latex" (
83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex
84 | echo.
85 | echo.Build finished; the LaTeX files are in _build/latex.
86 | goto end
87 | )
88 |
89 | if "%1" == "changes" (
90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes
91 | echo.
92 | echo.The overview file is in _build/changes.
93 | goto end
94 | )
95 |
96 | if "%1" == "linkcheck" (
97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck
98 | echo.
99 | echo.Link check complete; look for any errors in the above output ^
100 | or in _build/linkcheck/output.txt.
101 | goto end
102 | )
103 |
104 | if "%1" == "doctest" (
105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest
106 | echo.
107 | echo.Testing of doctests in the sources finished, look at the ^
108 | results in _build/doctest/output.txt.
109 | goto end
110 | )
111 |
112 | :end
113 |
--------------------------------------------------------------------------------
/package/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
3 |
4 |
5 | """
6 | Main file to launch the tests.
7 | """
8 |
9 | import sys
10 | import getopt
11 | import unittest
12 | import logging
13 |
14 |
15 | from test.test_All import MainTestSuite
16 |
17 | def usage():
18 | """
19 | Show/explain the options.
20 | """
21 | return """python main.py [OPTIONS]
22 |
23 | Options:
24 |
25 | -h or --help Print this help text
26 |
27 | -d Switch the logger to DEBUG mode.
28 |
29 | -v Switch the test to verbose mode.
30 | """
31 |
32 |
33 | def main(argv):
34 | """
35 | Launch all the test.
36 | """
37 | try:
38 | opts, args = getopt.getopt(argv[1:], "vdh", ["help"])
39 | except getopt.GetoptError:
40 | print(usage())
41 | sys.exit(2)
42 | loglevel = logging.ERROR
43 | test_verbosity = 1
44 | for o,a in opts:
45 | if o in ("-h","--help"):
46 | print(usage())
47 | sys.exit(0)
48 | elif o == "-d":
49 | loglevel = logging.DEBUG
50 | elif o == "-v":
51 | test_verbosity = 2
52 | logging.basicConfig(level= loglevel,
53 | format='%(asctime)s %(levelname)s %(message)s')
54 |
55 | # launch the testing process
56 | unittest.TextTestRunner(verbosity=test_verbosity).run(MainTestSuite)
57 |
58 |
59 |
60 | if __name__=="__main__":
61 | main(sys.argv)
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/package/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-
3 |
4 | """
5 | The setup.py script needed to build a .egg for an easier distribution
6 | and installation of yapsy.
7 |
8 | Requires 'Easy Install' to be installed :)
9 | see there: http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions
10 |
11 | Then to create a package run:
12 | $ python setup.py bdist_egg
13 |
14 | To use the generated .egg file then:
15 | easy_install Yapsy-{yapsy version}-py{python version}.egg
16 |
17 | Automagical stuff:
18 |
19 | - test everything::
20 |
21 | python setup.py test
22 |
23 | - build the packages (sources an egg) and upload all the stuff to pypi::
24 |
25 | python setup.py sdist bdist_egg upload
26 |
27 | - build the documentation
28 |
29 | python setup.py build_sphinx
30 | """
31 |
32 | import os
33 | from setuptools import setup
34 |
35 | # just in case setup.py is launched from elsewhere that the containing directory
36 | originalDir = os.getcwd()
37 | os.chdir(os.path.dirname(os.path.abspath(__file__)))
38 | try:
39 | setup(
40 | name = "Yapsy",
41 | version = __import__("yapsy").__version__,
42 | packages = ['yapsy'],
43 | package_dir = {'yapsy':'yapsy'},
44 |
45 | # the unit tests
46 | test_suite = "test.test_All.MainTestSuite",
47 |
48 | # metadata for upload to PyPI
49 | author = "Thibauld Nion",
50 | author_email = "thibauld@tibonihoo.net",
51 | description = "Yet another plugin system",
52 | license = "BSD",
53 | keywords = "plugin manager",
54 | url = "http://yapsy.sourceforge.net",
55 | # more details
56 | long_description = open("README.txt").read(),
57 | classifiers=['Development Status :: 5 - Production/Stable',
58 | 'Intended Audience :: Developers',
59 | 'License :: OSI Approved :: BSD License',
60 | 'Operating System :: OS Independent',
61 | 'Programming Language :: Python',
62 | 'Programming Language :: Python :: 3',
63 | 'Topic :: Software Development :: Libraries :: Python Modules'],
64 | platforms='All',
65 | )
66 |
67 | finally:
68 | os.chdir(originalDir)
69 |
--------------------------------------------------------------------------------
/package/test/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 | """
3 | Gathers the unittests of yapsy
4 | """
5 |
--------------------------------------------------------------------------------
/package/test/plugins/ConfigPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from yapsy.IPlugin import IPlugin
9 |
10 | class ConfigPlugin(IPlugin):
11 | """
12 | Try to use the methods with which it has been decorated.
13 | """
14 |
15 | def __init__(self):
16 | """
17 | init
18 | """
19 | # initialise parent class
20 | IPlugin.__init__(self)
21 |
22 |
23 | def activate(self):
24 | """
25 | Call the parent class's acivation method
26 | """
27 | IPlugin.activate(self)
28 | return
29 |
30 |
31 | def deactivate(self):
32 | """
33 | Just call the parent class's method
34 | """
35 | IPlugin.deactivate(self)
36 |
37 |
38 | def choseTestOption(self, value):
39 | """
40 | Set an option to a given value.
41 | """
42 | self.setConfigOption("Test",value)
43 |
44 | def checkTestOption(self):
45 | """
46 | Test if the test option is here.
47 | """
48 | return self.hasConfigOption("Test")
49 |
50 | def getTestOption(self):
51 | """
52 | Return the value of the test option.
53 | """
54 | return self.getConfigOption("Test")
55 |
56 |
57 |
--------------------------------------------------------------------------------
/package/test/plugins/ErroneousPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from yapsy.IPlugin import IPlugin
9 |
10 | from import_error import the_error_is_here
11 |
12 | class ErrorenousPlugin(IPlugin):
13 | """
14 | Only trigger the expected test results.
15 | """
16 |
17 | def __init__(self):
18 | """
19 | init
20 | """
21 | # initialise parent class
22 | IPlugin.__init__(self)
23 |
24 |
25 | def activate(self):
26 | """
27 | On activation tell that this has been successfull.
28 | """
29 | # get the automatic procedure from IPlugin
30 | IPlugin.activate(self)
31 | return
32 |
33 |
34 | def deactivate(self):
35 | """
36 | On deactivation check that the 'activated' flag was on then
37 | tell everything's ok to the test procedure.
38 | """
39 | IPlugin.deactivate(self)
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/package/test/plugins/LegacyMultiprocessPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | A simple multiprocessed plugin that echoes the content received to the parent
5 | """
6 |
7 | from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin
8 |
9 | class LegacyMultiprocessPlugin(IMultiprocessChildPlugin):
10 | """
11 | Only trigger the expected test results.
12 | """
13 |
14 | def __init__(self, parent_pipe):
15 | IMultiprocessChildPlugin.__init__(self, parent_pipe=parent_pipe)
16 |
17 | def run(self):
18 | content_from_parent = self.parent_pipe.recv()
19 | self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent))
20 |
--------------------------------------------------------------------------------
/package/test/plugins/SimpleMultiprocessPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | A simple multiprocessed plugin that echoes the content received to the parent
5 | """
6 |
7 | from yapsy.IMultiprocessPlugin import IMultiprocessPlugin
8 |
9 | class SimpleMultiprocessPlugin(IMultiprocessPlugin):
10 | """
11 | Only trigger the expected test results.
12 | """
13 |
14 | def __init__(self, parent_pipe):
15 | IMultiprocessPlugin.__init__(self, parent_pipe=parent_pipe)
16 |
17 | def run(self):
18 | content_from_parent = self.parent_pipe.recv()
19 | self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent))
20 |
21 |
--------------------------------------------------------------------------------
/package/test/plugins/SimplePlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from yapsy.IPlugin import IPlugin
9 |
10 | class SimplePlugin(IPlugin):
11 | """
12 | Only trigger the expected test results.
13 | """
14 |
15 | def __init__(self):
16 | """
17 | init
18 | """
19 | # initialise parent class
20 | IPlugin.__init__(self)
21 |
22 |
23 | def activate(self):
24 | """
25 | On activation tell that this has been successfull.
26 | """
27 | # get the automatic procedure from IPlugin
28 | IPlugin.activate(self)
29 | return
30 |
31 |
32 | def deactivate(self):
33 | """
34 | On deactivation check that the 'activated' flag was on then
35 | tell everything's ok to the test procedure.
36 | """
37 | IPlugin.deactivate(self)
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/package/test/plugins/VersionedPlugin10.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from test_settings import TEST_MESSAGE
9 | from yapsy.IPlugin import IPlugin
10 |
11 | class VersionedPlugin10(IPlugin):
12 | """
13 | Only trigger the expected test results.
14 | """
15 |
16 | def __init__(self):
17 | """
18 | init
19 | """
20 | # initialise parent class
21 | IPlugin.__init__(self)
22 | TEST_MESSAGE("Version 1.0")
23 |
24 | def activate(self):
25 | """
26 | On activation tell that this has been successfull.
27 | """
28 | # get the automatic procedure from IPlugin
29 | IPlugin.activate(self)
30 | TEST_MESSAGE("Activated Version 1.0!")
31 | return
32 |
33 |
34 | def deactivate(self):
35 | """
36 | On deactivation check that the 'activated' flag was on then
37 | tell everything's ok to the test procedure.
38 | """
39 | IPlugin.deactivate(self)
40 | TEST_MESSAGE("Deactivated Version 1.0!")
41 |
42 |
43 |
--------------------------------------------------------------------------------
/package/test/plugins/VersionedPlugin11.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from test_settings import TEST_MESSAGE
9 | from yapsy.IPlugin import IPlugin
10 |
11 | class VersionedPlugin11(IPlugin):
12 | """
13 | Only trigger the expected test results.
14 | """
15 |
16 | def __init__(self):
17 | """
18 | init
19 | """
20 | # initialise parent class
21 | IPlugin.__init__(self)
22 | TEST_MESSAGE("Version 1.1")
23 |
24 | def activate(self):
25 | """
26 | On activation tell that this has been successfull.
27 | """
28 | # get the automatic procedure from IPlugin
29 | IPlugin.activate(self)
30 | return
31 |
32 |
33 | def deactivate(self):
34 | """
35 | On deactivation check that the 'activated' flag was on then
36 | tell everything's ok to the test procedure.
37 | """
38 | IPlugin.deactivate(self)
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/package/test/plugins/VersionedPlugin111.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from test_settings import TEST_MESSAGE
9 | from yapsy.IPlugin import IPlugin
10 |
11 | class VersionedPlugin111(IPlugin):
12 | """
13 | Only trigger the expected test results.
14 | """
15 |
16 | def __init__(self):
17 | """
18 | init
19 | """
20 | # initialise parent class
21 | IPlugin.__init__(self)
22 | TEST_MESSAGE("Version 1.1.1")
23 |
24 | def activate(self):
25 | """
26 | On activation tell that this has been successfull.
27 | """
28 | # get the automatic procedure from IPlugin
29 | IPlugin.activate(self)
30 | return
31 |
32 |
33 | def deactivate(self):
34 | """
35 | On deactivation check that the 'activated' flag was on then
36 | tell everything's ok to the test procedure.
37 | """
38 | IPlugin.deactivate(self)
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/package/test/plugins/VersionedPlugin12.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from test_settings import TEST_MESSAGE
9 | from yapsy.IPlugin import IPlugin
10 |
11 | class VersionedPlugin12(IPlugin):
12 | """
13 | Only trigger the expected test results.
14 | """
15 |
16 | def __init__(self):
17 | """
18 | init
19 | """
20 | # initialise parent class
21 | IPlugin.__init__(self)
22 | TEST_MESSAGE("Version 1.2")
23 |
24 | def activate(self):
25 | """
26 | On activation tell that this has been successfull.
27 | """
28 | # get the automatic procedure from IPlugin
29 | IPlugin.activate(self)
30 | TEST_MESSAGE("Activated Version 1.2!")
31 | return
32 |
33 |
34 | def deactivate(self):
35 | """
36 | On deactivation check that the 'activated' flag was on then
37 | tell everything's ok to the test procedure.
38 | """
39 | IPlugin.deactivate(self)
40 | TEST_MESSAGE("Deactivated Version 1.2!")
41 |
--------------------------------------------------------------------------------
/package/test/plugins/VersionedPlugin12a1.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from test_settings import TEST_MESSAGE
9 | from yapsy.IPlugin import IPlugin
10 |
11 | class VersionedPlugin12a1(IPlugin):
12 | """
13 | Only trigger the expected test results.
14 | """
15 |
16 | def __init__(self):
17 | """
18 | init
19 | """
20 | # initialise parent class
21 | IPlugin.__init__(self)
22 | TEST_MESSAGE("Version 1.2a1")
23 |
24 | def activate(self):
25 | """
26 | On activation tell that this has been successfull.
27 | """
28 | # get the automatic procedure from IPlugin
29 | IPlugin.activate(self)
30 | TEST_MESSAGE("Activated Version 1.2a1!")
31 | return
32 |
33 |
34 | def deactivate(self):
35 | """
36 | On deactivation check that the 'activated' flag was on then
37 | tell everything's ok to the test procedure.
38 | """
39 | IPlugin.deactivate(self)
40 | TEST_MESSAGE("Deactivated Version 1.2a1!")
41 |
--------------------------------------------------------------------------------
/package/test/plugins/configplugin.yapsy-config-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Config Plugin
3 | Module = ConfigPlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin with configuration handling
10 |
--------------------------------------------------------------------------------
/package/test/plugins/configplugin.yapsy-filter-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Config Plugin
3 | Module = ConfigPlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin with configuration handling
10 |
--------------------------------------------------------------------------------
/package/test/plugins/erroneousplugin.yapsy-error-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Erroreous Plugin
3 | Module = ErroneousPlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://yapsy.sourceforge.net
9 | Description = A simple plugin trigger a syntax error for basic testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/legacymultiprocessplugin.multiprocess-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Legacy Multiprocess Plugin
3 | Module = LegacyMultiprocessPlugin
4 |
5 | [Documentation]
6 | Author = Pierre-Yves Langlois
7 | Version = 0.1
8 | Description = A minimal plugin to test multiprocessing
9 | Copyright = 2015
10 |
--------------------------------------------------------------------------------
/package/test/plugins/simplemultiprocessplugin.multiprocess-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Simple Multiprocess Plugin
3 | Module = SimpleMultiprocessPlugin
4 |
5 | [Documentation]
6 | Author = Pierre-Yves Langlois
7 | Version = 0.1
8 | Description = A minimal plugin to test multiprocessing
9 | Copyright = 2015
10 |
--------------------------------------------------------------------------------
/package/test/plugins/simpleplugin.yapsy-filter-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Simple Plugin
3 | Module = SimplePlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin usefull for basic testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/simpleplugin.yapsy-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Simple Plugin
3 | Module = SimplePlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin usefull for basic testing
10 | Copyright = 2014
11 |
--------------------------------------------------------------------------------
/package/test/plugins/versioned10.version-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Versioned Plugin
3 | Module = VersionedPlugin10
4 |
5 | [Documentation]
6 | Author = Rob McMullen
7 | Version = 1.0
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin for version testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/versioned11.version-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Versioned Plugin
3 | Module = VersionedPlugin11
4 |
5 | [Documentation]
6 | Author = Rob McMullen
7 | Version = 1.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin for version testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/versioned111.version-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Versioned Plugin
3 | Module = VersionedPlugin111
4 |
5 | [Documentation]
6 | Author = Rob McMullen
7 | Version = 1.1.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin for version testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/versioned12.version-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Versioned Plugin
3 | Module = VersionedPlugin12
4 |
5 | [Documentation]
6 | Author = Rob McMullen
7 | Version = 1.2
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin for version testing
10 |
--------------------------------------------------------------------------------
/package/test/plugins/versioned12a1.version-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Versioned Plugin
3 | Module = VersionedPlugin12a1
4 |
5 | [Documentation]
6 | Author = Rob McMullen
7 | Version = 1.2a1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin for version testing
10 |
--------------------------------------------------------------------------------
/package/test/pluginsasdirs/SimplePlugin/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | This is certainly the second simplest plugin ever.
5 | """
6 |
7 | from yapsy.IPlugin import IPlugin
8 |
9 | class SimplePlugin(IPlugin):
10 | """
11 | Only trigger the expected test results.
12 | """
13 |
14 | def __init__(self):
15 | """
16 | init
17 | """
18 | # initialise parent class
19 | IPlugin.__init__(self)
20 |
21 |
22 | def activate(self):
23 | """
24 | On activation tell that this has been successfull.
25 | """
26 | # get the automatic procedure from IPlugin
27 | IPlugin.activate(self)
28 | return
29 |
30 |
31 | def deactivate(self):
32 | """
33 | On deactivation check that the 'activated' flag was on then
34 | tell everything's ok to the test procedure.
35 | """
36 | IPlugin.deactivate(self)
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/package/test/pluginsasdirs/simpleplugin.yapsy-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Simple Plugin
3 | Module = SimplePlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin usefull for basic testing
10 |
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/AutoInstallPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from yapsy.IPlugin import IPlugin
9 |
10 | class AutoInstallPlugin(IPlugin):
11 | """
12 | Only trigger the expected test results.
13 | """
14 |
15 | def __init__(self):
16 | """
17 | init
18 | """
19 | # initialise parent class
20 | IPlugin.__init__(self)
21 |
22 |
23 | def activate(self):
24 | """
25 | On activation tell that this has been successfull.
26 | """
27 | # get the automatic procedure from IPlugin
28 | IPlugin.activate(self)
29 | return
30 |
31 |
32 | def deactivate(self):
33 | """
34 | On deactivation check that the 'activated' flag was on then
35 | tell everything's ok to the test procedure.
36 | """
37 | IPlugin.deactivate(self)
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/autoinstallZIPplugin.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tibonihoo/yapsy/6b487b04affb19ab40adbbc87827668bea0abcee/package/test/pluginstoinstall/autoinstallZIPplugin.zip
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/autoinstalldirplugin.yapsy-autoinstall-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Auto Install Dir Plugin
3 | Module = autoinstalldirplugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin usefull for basic testing
10 |
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/autoinstalldirplugin/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | This is certainly the second simplest plugin ever.
6 | """
7 |
8 | from yapsy.IPlugin import IPlugin
9 |
10 | class AutoInstallDirPlugin(IPlugin):
11 | """
12 | Only trigger the expected test results.
13 | """
14 |
15 | def __init__(self):
16 | """
17 | init
18 | """
19 | # initialise parent class
20 | IPlugin.__init__(self)
21 |
22 |
23 | def activate(self):
24 | """
25 | On activation tell that this has been successfull.
26 | """
27 | # get the automatic procedure from IPlugin
28 | IPlugin.activate(self)
29 | return
30 |
31 |
32 | def deactivate(self):
33 | """
34 | On deactivation check that the 'activated' flag was on then
35 | tell everything's ok to the test procedure.
36 | """
37 | IPlugin.deactivate(self)
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/package/test/pluginstoinstall/autoinstallplugin.yapsy-autoinstall-plugin:
--------------------------------------------------------------------------------
1 | [Core]
2 | Name = Auto Install Plugin
3 | Module = AutoInstallPlugin
4 |
5 | [Documentation]
6 | Author = Thibauld Nion
7 | Version = 0.1
8 | Website = http://mathbench.sourceforge.net
9 | Description = A simple plugin usefull for basic testing
10 |
--------------------------------------------------------------------------------
/package/test/test_All.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | The test suite that binds them all...
5 | """
6 |
7 | import sys
8 | import os
9 | import unittest
10 |
11 | # set correct loading path for test files
12 | sys.path.append(
13 | os.path.dirname(
14 | os.path.abspath(__file__)))
15 |
16 |
17 | # load the tests
18 | from . import test_SimplePlugin
19 | from . import test_Singleton
20 | from . import test_ConfigPlugin
21 | from . import test_VersionedPlugin
22 | from . import test_AutoInstallPlugin
23 | from . import test_FilterPlugin
24 | from . import test_ErrorInPlugin
25 | from . import test_PluginFileLocator
26 | from . import test_PluginInfo
27 | from . import test_SimpleMultiprocessPlugin
28 |
29 | # add them to a common test suite
30 | MainTestSuite = unittest.TestSuite(
31 | [ # add the tests suites below
32 | test_SimplePlugin.suite,
33 | test_Singleton.suite,
34 | test_ConfigPlugin.suite,
35 | test_VersionedPlugin.suite,
36 | test_AutoInstallPlugin.suite,
37 | test_FilterPlugin.suite,
38 | test_ErrorInPlugin.suite,
39 | test_PluginFileLocator.suite,
40 | test_PluginInfo.suite,
41 | test_SimpleMultiprocessPlugin.suite,
42 | ])
43 |
44 |
--------------------------------------------------------------------------------
/package/test/test_AutoInstallPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 | import unittest
5 | import sys
6 | import os
7 | import shutil
8 |
9 | from yapsy.AutoInstallPluginManager import AutoInstallPluginManager
10 |
11 | class AutoInstallTestsCase(unittest.TestCase):
12 | """
13 | Test the correct installation and loading of a simple plugin.
14 | """
15 |
16 |
17 | def setUp(self):
18 | """
19 | init
20 | """
21 | # create the plugin manager
22 | self.storing_dir = os.path.join(
23 | os.path.dirname(os.path.abspath(__file__)),"plugins")
24 | self.pluginManager = AutoInstallPluginManager(
25 | self.storing_dir,
26 | directories_list=[self.storing_dir],
27 | plugin_info_ext="yapsy-autoinstall-plugin")
28 | # load the plugins that may be found
29 | self.pluginManager.collectPlugins()
30 | # Will be used later
31 | self.plugin_info = None
32 | self.new_plugins_waiting_dir = os.path.join(
33 | os.path.dirname(os.path.abspath(__file__)),"pluginstoinstall")
34 |
35 |
36 | def tearDown(self):
37 | """
38 | Clean the plugin installation directory.
39 | """
40 | try:
41 | os.remove(os.path.join(self.pluginManager.plugins_places[0],
42 | "autoinstallplugin.yapsy-autoinstall-plugin"))
43 | except OSError:
44 | pass
45 | try:
46 | os.remove(os.path.join(self.pluginManager.plugins_places[0],
47 | "AutoInstallPlugin.py"))
48 | except OSError:
49 | pass
50 | try:
51 | os.remove(os.path.join(self.pluginManager.plugins_places[0],
52 | "autoinstalldirplugin.yapsy-autoinstall-plugin"))
53 | except OSError:
54 | pass
55 | try:
56 | shutil.rmtree(os.path.join(self.pluginManager.plugins_places[0],
57 | "autoinstalldirplugin"))
58 | except OSError:
59 | pass
60 |
61 |
62 | def plugin_loading_check_none(self):
63 | """
64 | Test that no plugin has been loaded.
65 | """
66 | # check nb of categories
67 | self.assertEqual(len(self.pluginManager.getCategories()),1)
68 | sole_category = self.pluginManager.getCategories()[0]
69 | # check the number of plugins
70 | self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),0)
71 |
72 | def plugin_loading_check(self,new_plugin_name):
73 | """
74 | Test if the correct plugin has been loaded.
75 | """
76 | if self.plugin_info is None:
77 | # check nb of categories
78 | self.assertEqual(len(self.pluginManager.getCategories()),1)
79 | sole_category = self.pluginManager.getCategories()[0]
80 | # check the number of plugins
81 | self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1)
82 | self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0]
83 | # test that the name of the plugin has been correctly defined
84 | self.assertEqual(self.plugin_info.name,new_plugin_name)
85 | self.assertEqual(sole_category,self.plugin_info.category)
86 | else:
87 | self.assertTrue(True)
88 |
89 | def testGetSetInstallDir(self):
90 | """
91 | Test getting and setting install dir.
92 | """
93 | self.assertEqual(self.storing_dir,self.pluginManager.getInstallDir())
94 | custom_install_dir = os.path.join("mouf", "bla")
95 | self.pluginManager.setInstallDir(custom_install_dir)
96 | self.assertEqual(custom_install_dir, self.pluginManager.getInstallDir())
97 |
98 |
99 | def testNoneLoaded(self):
100 | """
101 | Test if the correct plugin has been loaded.
102 | """
103 | self.plugin_loading_check_none()
104 |
105 | def testInstallFile(self):
106 | """
107 | Test if the correct plugin (defined by a file) can be installed and loaded.
108 | """
109 | install_success = self.pluginManager.install(self.new_plugins_waiting_dir,
110 | "autoinstallplugin.yapsy-autoinstall-plugin")
111 | self.assertTrue(install_success)
112 | self.pluginManager.collectPlugins()
113 | self.plugin_loading_check("Auto Install Plugin")
114 |
115 |
116 | def testInstallDir(self):
117 | """
118 | Test if the correct plugin (define by a directory) can be installed and loaded.
119 | """
120 | install_success = self.pluginManager.install(self.new_plugins_waiting_dir,
121 | "autoinstalldirplugin.yapsy-autoinstall-plugin")
122 | self.assertTrue(install_success)
123 | self.pluginManager.collectPlugins()
124 | self.plugin_loading_check("Auto Install Dir Plugin")
125 |
126 |
127 | def testActivationAndDeactivation(self):
128 | """
129 | Test if the activation procedure works.
130 | """
131 | install_success = self.pluginManager.install(self.new_plugins_waiting_dir,
132 | "autoinstallplugin.yapsy-autoinstall-plugin")
133 | self.assertTrue(install_success)
134 | self.pluginManager.collectPlugins()
135 | self.plugin_loading_check("Auto Install Plugin")
136 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
137 | self.pluginManager.activatePluginByName(self.plugin_info.name,
138 | self.plugin_info.category)
139 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
140 | self.pluginManager.deactivatePluginByName(self.plugin_info.name,
141 | self.plugin_info.category)
142 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
143 |
144 | class AutoInstallZIPTestsCase(unittest.TestCase):
145 | """
146 | Test the correct installation and loading of a zipped plugin.
147 | """
148 |
149 |
150 | def setUp(self):
151 | """
152 | init
153 | """
154 | # create the plugin manager
155 | storing_dir = os.path.join(
156 | os.path.dirname(os.path.abspath(__file__)),"plugins")
157 | self.pluginManager = AutoInstallPluginManager(
158 | storing_dir,
159 | directories_list=[storing_dir],
160 | plugin_info_ext="yapsy-autoinstall-plugin")
161 | # load the plugins that may be found
162 | self.pluginManager.collectPlugins()
163 | # Will be used later
164 | self.plugin_info = None
165 | self.new_plugins_waiting_dir = os.path.join(
166 | os.path.dirname(os.path.abspath(__file__)),"pluginstoinstall")
167 |
168 |
169 | def tearDown(self):
170 | """
171 | Clean the plugin installation directory.
172 | """
173 | try:
174 | os.remove(os.path.join(self.pluginManager.plugins_places[0],
175 | "autoinstallzipplugin.yapsy-autoinstall-plugin"))
176 | except OSError:
177 | pass
178 | try:
179 | shutil.rmtree(os.path.join(self.pluginManager.plugins_places[0],
180 | "autoinstallzipplugin"))
181 | except OSError:
182 | pass
183 |
184 |
185 | def plugin_loading_check_none(self):
186 | """
187 | Test that no plugin has been loaded.
188 | """
189 | # check nb of categories
190 | self.assertEqual(len(self.pluginManager.getCategories()),1)
191 | sole_category = self.pluginManager.getCategories()[0]
192 | # check the number of plugins
193 | self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),0)
194 |
195 | def plugin_loading_check(self,new_plugin_name):
196 | """
197 | Test if the correct plugin has been loaded.
198 | """
199 | if self.plugin_info is None:
200 | # check nb of categories
201 | self.assertEqual(len(self.pluginManager.getCategories()),1)
202 | sole_category = self.pluginManager.getCategories()[0]
203 | # check the number of plugins
204 | self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1)
205 | self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0]
206 | # test that the name of the plugin has been correctly defined
207 | self.assertEqual(self.plugin_info.name,new_plugin_name)
208 | self.assertEqual(sole_category,self.plugin_info.category)
209 | else:
210 | self.assertTrue(True)
211 |
212 | def testNoneLoaded(self):
213 | """
214 | Test if the correct plugin has been loaded.
215 | """
216 | self.plugin_loading_check_none()
217 |
218 | def testInstallZIP(self):
219 | """
220 | Test if the correct plugin (define by a zip file) can be installed and loaded.
221 | """
222 | test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallZIPplugin.zip")
223 | if sys.version_info < (2, 6):
224 | self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file)
225 | return
226 | install_success = self.pluginManager.installFromZIP(test_file)
227 | self.assertTrue(install_success)
228 | self.pluginManager.collectPlugins()
229 | self.plugin_loading_check("Auto Install ZIP Plugin")
230 |
231 | def testInstallZIPFailOnWrongZip(self):
232 | """
233 | Test if, when the zip file does not contain what is required the installation fails.
234 | """
235 | test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallWRONGzipplugin.zip")
236 | if sys.version_info < (2, 6):
237 | self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file)
238 | return
239 | install_success = self.pluginManager.installFromZIP(test_file)
240 | self.assertFalse(install_success)
241 | self.pluginManager.collectPlugins()
242 | self.plugin_loading_check_none()
243 |
244 | def testInstallZIPFailOnUnexistingFile(self):
245 | """
246 | Test if, when the zip file is not a file.
247 | """
248 | test_file = os.path.join(self.new_plugins_waiting_dir,"doesNotExists.zip")
249 | if sys.version_info < (2, 6):
250 | self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file)
251 | return
252 | install_success = self.pluginManager.installFromZIP(test_file)
253 | self.assertFalse(install_success)
254 | self.pluginManager.collectPlugins()
255 | self.plugin_loading_check_none()
256 |
257 | def testInstallZIPFailOnNotAZipFile(self):
258 | """
259 | Test if, when the zip file is not a valid zip.
260 | """
261 | test_file = os.path.join(self.new_plugins_waiting_dir,"AutoInstallPlugin.py")
262 | if sys.version_info < (2, 6):
263 | self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file)
264 | return
265 | install_success = self.pluginManager.installFromZIP(test_file)
266 | self.assertFalse(install_success)
267 | self.pluginManager.collectPlugins()
268 | self.plugin_loading_check_none()
269 |
270 | def testActivationAndDeactivation(self):
271 | """
272 | Test if the activation procedure works.
273 | """
274 | test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallZIPplugin.zip")
275 | if sys.version_info < (2, 6):
276 | self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file)
277 | return
278 | install_success = self.pluginManager.installFromZIP(test_file)
279 | self.assertTrue(install_success)
280 | self.pluginManager.collectPlugins()
281 | self.plugin_loading_check("Auto Install ZIP Plugin")
282 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
283 | self.pluginManager.activatePluginByName(self.plugin_info.name,
284 | self.plugin_info.category)
285 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
286 | self.pluginManager.deactivatePluginByName(self.plugin_info.name,
287 | self.plugin_info.category)
288 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
289 |
290 |
291 |
292 | suite = unittest.TestSuite([
293 | unittest.TestLoader().loadTestsFromTestCase(AutoInstallTestsCase),
294 | unittest.TestLoader().loadTestsFromTestCase(AutoInstallZIPTestsCase),
295 | ])
296 |
--------------------------------------------------------------------------------
/package/test/test_ConfigPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 |
5 | import os
6 | import unittest
7 |
8 | from configparser import ConfigParser
9 | from yapsy.ConfigurablePluginManager import ConfigurablePluginManager
10 |
11 |
12 | class ConfigTestMixin:
13 |
14 | def plugin_loading_check(self):
15 | """
16 | Test if the correct plugin has been loaded.
17 | """
18 | if self.plugin_info is None:
19 | # check nb of categories
20 | self.assertEqual(len(self.pluginManager.getCategories()),1)
21 | sole_category = self.pluginManager.getCategories()[0]
22 | # check the number of plugins
23 | self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1)
24 | self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0]
25 | # test that the name of the plugin has been correctly defined
26 | self.assertEqual(self.plugin_info.name,"Config Plugin")
27 | self.assertEqual(sole_category,self.plugin_info.category)
28 | else:
29 | self.assertTrue(True)
30 |
31 | def plugin_activate(self):
32 | """
33 | Activate the plugin with basic checking
34 | """
35 | self.plugin_loading_check()
36 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
37 | self.pluginManager.activatePluginByName(self.plugin_info.name,
38 | self.plugin_info.category)
39 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
40 |
41 |
42 | class ConfigTestCase(unittest.TestCase, ConfigTestMixin):
43 | """
44 | Test the correct loading of a plugin that uses a configuration
45 | file through a ConfigurablePluginManager as well as basic
46 | commands.
47 | """
48 |
49 | CONFIG_FILE = test_settings.TEMP_CONFIG_FILE_NAME
50 |
51 | def setUp(self):
52 | """
53 | init
54 | """
55 | # create a config file
56 | self.config_file = self.CONFIG_FILE
57 | self.config_parser = ConfigParser()
58 | self.plugin_info = None
59 | # create the plugin manager
60 | self.pluginManager = ConfigurablePluginManager(
61 | directories_list=[os.path.join(
62 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
63 | plugin_info_ext="yapsy-config-plugin",
64 | configparser_instance=self.config_parser,
65 | config_change_trigger=self.update_config)
66 | # load the plugins that may be found
67 | self.pluginManager.collectPlugins()
68 |
69 | def tearDown(self):
70 | """
71 | When the test has been performed erase the temp file.
72 | """
73 | if os.path.isfile(self.config_file):
74 | os.remove(self.config_file)
75 |
76 | def testConfigurationFileExistence(self):
77 | """
78 | Test if the configuration file has been properly written.
79 | """
80 | # activate the only loaded plugin
81 | self.plugin_activate()
82 | # get rid of the plugin manager and create a new one
83 | del self.pluginManager
84 | del self.config_parser
85 | self.config_parser = ConfigParser()
86 | self.config_parser.read(self.config_file)
87 | self.assertTrue(self.config_parser.has_section("Plugin Management"))
88 | self.assertTrue(self.config_parser.has_option("Plugin Management",
89 | "default_plugins_to_load"))
90 | self.pluginManager = ConfigurablePluginManager(
91 | directories_list=[os.path.join(
92 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
93 | plugin_info_ext="yapsy-config-plugin",
94 | configparser_instance=self.config_parser,
95 | config_change_trigger=self.update_config)
96 | self.pluginManager.collectPlugins()
97 | self.plugin_loading_check()
98 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
99 | self.pluginManager.deactivatePluginByName(self.plugin_info.name,
100 | self.plugin_info.category)
101 | # check that activating the plugin once again, won't cause an error
102 | self.pluginManager.activatePluginByName(self.plugin_info.name,
103 | self.plugin_info.category)
104 | # Will be used later
105 | self.plugin_info = None
106 |
107 | def testLoaded(self):
108 | """
109 | Test if the correct plugin has been loaded.
110 | """
111 | self.plugin_loading_check()
112 |
113 | def testActivationAndDeactivation(self):
114 | """
115 | Test if the activation/deactivaion procedures work.
116 | """
117 | self.plugin_activate()
118 | self.pluginManager.deactivatePluginByName(self.plugin_info.name,
119 | self.plugin_info.category)
120 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
121 |
122 | def testPluginOptions(self):
123 | """
124 | Test is the plugin can register and access options from the
125 | ConfigParser.
126 | """
127 | self.plugin_activate()
128 | plugin = self.plugin_info.plugin_object
129 | plugin.choseTestOption("voila")
130 | self.assertTrue(plugin.checkTestOption())
131 | self.assertEqual(plugin.getTestOption(),"voila")
132 |
133 | def update_config(self):
134 | """
135 | Write the content of the ConfigParser in a file.
136 | """
137 | cf = open(self.config_file,"a")
138 | self.config_parser.write(cf)
139 | cf.close()
140 |
141 |
142 | class ConfigurablePMWithDefaultChangeTriggerTestCase(unittest.TestCase, ConfigTestMixin):
143 | """Test the correctness of default values of args specific to the
144 | ConfigurablePM in its construtor.
145 | """
146 |
147 | def setUp(self):
148 | """
149 | init
150 | """
151 | # create a config file
152 | self.config_parser = ConfigParser()
153 | self.plugin_info = None
154 | # create the plugin manager
155 | self.pluginManager = ConfigurablePluginManager(
156 | directories_list=[os.path.join(
157 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
158 | plugin_info_ext="yapsy-config-plugin",
159 | configparser_instance=self.config_parser)
160 | # load the plugins that may be found
161 | self.pluginManager.collectPlugins()
162 |
163 | def testPluginOptions(self):
164 | """
165 | Test is the plugin can register and access options from the
166 | ConfigParser.
167 | """
168 | self.plugin_activate()
169 | plugin = self.plugin_info.plugin_object
170 | plugin.choseTestOption("voila")
171 | self.assertTrue(plugin.checkTestOption())
172 | self.assertEqual(plugin.getTestOption(),"voila")
173 |
174 |
175 |
176 | suite = unittest.TestSuite([
177 | unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase),
178 | unittest.TestLoader().loadTestsFromTestCase(ConfigurablePMWithDefaultChangeTriggerTestCase),
179 | ])
180 |
--------------------------------------------------------------------------------
/package/test/test_ErrorInPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 |
5 | import os
6 | import unittest
7 | import logging
8 |
9 | from yapsy.PluginManager import PluginManager
10 | from yapsy import log
11 |
12 | class ErrorTestCase(unittest.TestCase):
13 | """
14 | Test the handling of errors during plugin load.
15 | """
16 |
17 | def testTwoStepsLoadWithError(self):
18 | """
19 | Test loading the plugins in two steps in order to collect more
20 | deltailed informations and take care of an erroneous plugin.
21 | """
22 | spm = PluginManager(directories_list=[
23 | os.path.join(
24 | os.path.dirname(os.path.abspath(__file__)),"plugins")
25 | ], plugin_info_ext="yapsy-error-plugin")
26 | # trigger the first step to look up for plugins
27 | spm.locatePlugins()
28 | # make full use of the "feedback" the loadPlugins can give
29 | # - set-up the callback function that will be called *before*
30 | # loading each plugin
31 | callback_infos = []
32 | def preload_cbk(i_plugin_info):
33 | callback_infos.append(i_plugin_info)
34 | callback_after_infos = []
35 | def postload_cbk(i_plugin_info):
36 | callback_after_infos.append(i_plugin_info)
37 | # - gather infos about the processed plugins (loaded or not)
38 | # and for the test, monkey patch the logger
39 | originalLogLevel = log.getEffectiveLevel()
40 | log.setLevel(logging.ERROR)
41 | errorLogCallFlag = [False]
42 | def errorMock(*args,**kwargs):
43 | errorLogCallFlag[0]=True
44 | originalErrorMethod = log.error
45 | log.error = errorMock
46 | try:
47 | loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk)
48 | finally:
49 | log.setLevel(originalLogLevel)
50 | log.error = originalErrorMethod
51 | self.assertTrue(errorLogCallFlag[0])
52 | self.assertEqual(len(loadedPlugins),1)
53 | self.assertEqual(len(callback_infos),1)
54 | self.assertTrue(isinstance(callback_infos[0].error,tuple))
55 | self.assertEqual(loadedPlugins[0],callback_infos[0])
56 | self.assertTrue(issubclass(callback_infos[0].error[0],ImportError))
57 | self.assertEqual(len(callback_after_infos),0)
58 | # check that the getCategories works
59 | self.assertEqual(len(spm.getCategories()),1)
60 | sole_category = spm.getCategories()[0]
61 | # check the getPluginsOfCategory
62 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0)
63 |
64 |
65 |
66 | suite = unittest.TestSuite([
67 | unittest.TestLoader().loadTestsFromTestCase(ErrorTestCase),
68 | ])
69 |
--------------------------------------------------------------------------------
/package/test/test_FilterPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 | from .test_settings import TEST_MESSAGE
5 | import unittest
6 | import os
7 | import re
8 |
9 | from yapsy.FilteredPluginManager import FilteredPluginManager
10 |
11 |
12 | class testFilter(FilteredPluginManager):
13 | """
14 | Test filter class.
15 | Refused to load plugins whose Name starts with 'C'.
16 | """
17 | _bannednames = re.compile(r"^C")
18 |
19 | def isPluginOk(self,info):
20 | return not self._bannednames.match(info.name)
21 |
22 |
23 | class FilteredTestsCase(unittest.TestCase):
24 | """
25 | Test the correct loading of a simple plugin as well as basic
26 | commands.
27 | """
28 |
29 | def setUp(self):
30 | """
31 | init
32 | """
33 | # create the plugin manager
34 | # print os.path.join(os.path.dirname(os.path.abspath(__file__)),"plugins")
35 | self.filteredPluginManager = testFilter(
36 | directories_list=[os.path.join(
37 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
38 | plugin_info_ext="yapsy-filter-plugin",
39 | )
40 | # load the plugins that may be found
41 | self.filteredPluginManager.collectPlugins()
42 | # Will be used later
43 | self.plugin_info = None
44 |
45 | def plugin_loading_check(self):
46 | """
47 | Test if the correct plugins have been loaded.
48 | """
49 | # check nb of categories
50 | self.assertEqual(len(self.filteredPluginManager.getCategories()),1)
51 | sole_category = self.filteredPluginManager.getCategories()[0]
52 | # check the number of plugins
53 | self.assertEqual(len(self.filteredPluginManager.getPluginsOfCategory(sole_category)),1)
54 | plugins = self.filteredPluginManager.getPluginsOfCategory(sole_category)
55 | for plugin_info in plugins:
56 | TEST_MESSAGE("plugin info: %s" % plugin_info)
57 | self.plugin_info = plugin_info
58 | self.assertTrue(self.plugin_info)
59 | self.assertEqual(self.plugin_info.name,"Simple Plugin")
60 | self.assertEqual(sole_category,self.plugin_info.category)
61 |
62 | def testLoaded(self):
63 | """
64 | Test if the correct plugin has been loaded.
65 | """
66 | self.plugin_loading_check()
67 |
68 |
69 | def testActivationAndDeactivation(self):
70 | """
71 | Test if the activation procedure works.
72 | """
73 | self.plugin_loading_check()
74 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
75 | TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object)
76 | self.plugin_info.plugin_object.activate()
77 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
78 | self.plugin_info.plugin_object.deactivate()
79 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
80 |
81 |
82 | def testRejectedList(self):
83 | """
84 | Test if the list of rejected plugins is correct.
85 | """
86 | for plugin in self.filteredPluginManager.getRejectedPlugins():
87 | TEST_MESSAGE("plugin info: %s" % plugin[2])
88 | self.assertEqual(plugin[2].name,"Config Plugin")
89 |
90 | def testRejectedStable(self):
91 | reject1 = list(self.filteredPluginManager.getRejectedPlugins())
92 | self.filteredPluginManager.collectPlugins()
93 | reject2 = list(self.filteredPluginManager.getRejectedPlugins())
94 | self.assertEqual(len(reject1),len(reject2))
95 |
96 |
97 | def testRejectPlugin(self):
98 | self.filteredPluginManager.locatePlugins()
99 | rejected = self.filteredPluginManager.rejectedPlugins
100 | #If this fails the test in not meaningful..
101 | self.assertTrue(len(rejected) > 0)
102 | nrRejected = len(rejected)
103 | for plugin in rejected:
104 | self.filteredPluginManager.rejectPluginCandidate(plugin)
105 | self.assertEqual(nrRejected,len(self.filteredPluginManager.rejectedPlugins))
106 |
107 | def testRemovePlugin(self):
108 | self.filteredPluginManager.locatePlugins()
109 | rejected = self.filteredPluginManager.rejectedPlugins
110 | nrCandidates = len(self.filteredPluginManager.getPluginCandidates())
111 | #If this fails the test in not meaningful..
112 | self.assertTrue(len(rejected) > 0)
113 | for plugin in rejected:
114 | self.filteredPluginManager.removePluginCandidate(plugin)
115 | self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins))
116 | self.assertEqual( nrCandidates , len(self.filteredPluginManager.getPluginCandidates()))
117 |
118 | def testAppendRejectedPlugin(self):
119 | self.filteredPluginManager.locatePlugins()
120 | rejected = self.filteredPluginManager.getRejectedPlugins()
121 | nrRejected = len(rejected)
122 | nrCandidates = len(self.filteredPluginManager.getPluginCandidates())
123 |
124 | #If this fails the test in not meaningful..
125 | self.assertTrue(len(rejected) > 0)
126 | #Remove the rejected plugins into out own list.
127 | for plugin in rejected:
128 | self.filteredPluginManager.removePluginCandidate(plugin)
129 | self.assertEqual(len(self.filteredPluginManager.getRejectedPlugins()),0)
130 |
131 | ##Now Actually test Append.
132 | for plugin in rejected:
133 | self.filteredPluginManager.appendPluginCandidate(plugin)
134 | self.assertEqual(nrRejected ,len(self.filteredPluginManager.rejectedPlugins))
135 | self.assertEqual(nrCandidates , len(self.filteredPluginManager.getPluginCandidates()))
136 |
137 | def testAppendOkPlugins(self):
138 | self.filteredPluginManager.locatePlugins()
139 | rejected = self.filteredPluginManager.getRejectedPlugins()
140 | nrRejected = len(rejected)
141 | nrCandidates = len(self.filteredPluginManager.getPluginCandidates())
142 |
143 | #If this fails the test in not meaningful..
144 | self.assertTrue(len(rejected) > 0)
145 | #Remove the rejected plugins again.
146 | for plugin in rejected:
147 | self.filteredPluginManager.removePluginCandidate(plugin)
148 | self.assertEqual(len(self.filteredPluginManager.getRejectedPlugins()),0)
149 |
150 | for plugin in rejected:
151 | #change the name so it is acceptable.
152 | plugin[2].name = "X" + plugin[2].name[1:]
153 | self.filteredPluginManager.appendPluginCandidate(plugin)
154 | self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins))
155 | self.assertEqual(nrRejected + nrCandidates , len(self.filteredPluginManager.getPluginCandidates()))
156 |
157 |
158 |
159 |
160 | def testUnrejectPlugin(self):
161 | self.filteredPluginManager.locatePlugins()
162 | rejected = self.filteredPluginManager.rejectedPlugins
163 | nrRejected = len(rejected)
164 | nrCandidates = len(self.filteredPluginManager.getPluginCandidates())
165 | #If this fails the test in not meaningful..
166 | self.assertTrue(len(rejected) > 0)
167 | for plugin in rejected:
168 | self.filteredPluginManager.unrejectPluginCandidate(plugin)
169 | self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins))
170 | self.assertEqual( nrRejected + nrCandidates ,
171 | len(self.filteredPluginManager.getPluginCandidates()))
172 |
173 |
174 | class FilteredWithMonkeyPathTestsCase(unittest.TestCase):
175 | """
176 | Test the correct loading oand filtering of plugins when the FilteredPluginManager is just monkey-patched
177 | """
178 |
179 | def setUp(self):
180 | """
181 | init
182 | """
183 | # create the plugin manager
184 | # print os.path.join(os.path.dirname(os.path.abspath(__file__)),"plugins")
185 | self.filteredPluginManager = FilteredPluginManager(
186 | directories_list=[os.path.join(
187 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
188 | plugin_info_ext="yapsy-filter-plugin",
189 | )
190 | self.filteredPluginManager.isPluginOk = lambda info:not re.match("^C",info.name)
191 | # load the plugins that may be found
192 | self.filteredPluginManager.collectPlugins()
193 | # Will be used later
194 | self.plugin_info = None
195 |
196 | def plugin_loading_check(self):
197 | """
198 | Test if the correct plugins have been loaded.
199 | """
200 | # check nb of categories
201 | self.assertEqual(len(self.filteredPluginManager.getCategories()),1)
202 | sole_category = self.filteredPluginManager.getCategories()[0]
203 | # check the number of plugins
204 | self.assertEqual(len(self.filteredPluginManager.getPluginsOfCategory(sole_category)),1)
205 | plugins = self.filteredPluginManager.getPluginsOfCategory(sole_category)
206 | for plugin_info in plugins:
207 | TEST_MESSAGE("plugin info: %s" % plugin_info)
208 | self.plugin_info = plugin_info
209 | self.assertTrue(self.plugin_info)
210 | self.assertEqual(self.plugin_info.name,"Simple Plugin")
211 | self.assertEqual(sole_category,self.plugin_info.category)
212 |
213 | def testLoaded(self):
214 | """
215 | Test if the correct plugin has been loaded.
216 | """
217 | self.plugin_loading_check()
218 |
219 |
220 | def testActivationAndDeactivation(self):
221 | """
222 | Test if the activation procedure works.
223 | """
224 | self.plugin_loading_check()
225 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
226 | TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object)
227 | self.plugin_info.plugin_object.activate()
228 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
229 | self.plugin_info.plugin_object.deactivate()
230 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
231 |
232 |
233 | def testRejectedList(self):
234 | """
235 | Test if the list of rejected plugins is correct.
236 | """
237 | for plugin in self.filteredPluginManager.getRejectedPlugins():
238 | TEST_MESSAGE("plugin info: %s" % plugin[2])
239 | self.assertEqual(plugin[2].name,"Config Plugin")
240 |
241 | suite = unittest.TestSuite([
242 | unittest.TestLoader().loadTestsFromTestCase(FilteredTestsCase),
243 | unittest.TestLoader().loadTestsFromTestCase(FilteredWithMonkeyPathTestsCase),
244 | ])
245 |
--------------------------------------------------------------------------------
/package/test/test_PluginInfo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | import test_settings
4 | from configparser import ConfigParser
5 | import unittest
6 |
7 |
8 | from yapsy.PluginInfo import PluginInfo
9 | from packaging.version import Version
10 |
11 |
12 | class PluginInfoTest(unittest.TestCase):
13 | """
14 | Test basic manipulations of PluginInfo.
15 | """
16 |
17 | def testDefaultValuesAndAccessors(self):
18 | pi = PluginInfo("mouf","/bla/mouf")
19 | self.assertEqual("mouf",pi.name)
20 | self.assertEqual("/bla/mouf",pi.path)
21 | self.assertEqual(None,pi.plugin_object)
22 | self.assertEqual([],pi.categories)
23 | self.assertEqual(None,pi.error)
24 | self.assertEqual(Version("0.0"),pi.version)
25 | self.assertEqual("Unknown",pi.author)
26 | self.assertEqual("Unknown",pi.copyright)
27 | self.assertEqual("None",pi.website)
28 | self.assertEqual("",pi.description)
29 | self.assertEqual("UnknownCategory",pi.category)
30 |
31 | def testDetailsAccessors(self):
32 | pi = PluginInfo("mouf","/bla/mouf")
33 | details = ConfigParser()
34 | details.add_section("Core")
35 | details.set("Core","Name","hop")
36 | details.set("Core","Module","/greuh")
37 | details.add_section("Documentation")
38 | details.set("Documentation","Author","me")
39 | pi.details = details
40 | # Beware this is not so obvious: the plugin info still points
41 | # (and possibly modifies) the same instance of ConfigParser
42 | self.assertEqual(details,pi.details)
43 | # also the name and path are kept to their original value when
44 | # the details is set in one go.
45 | self.assertEqual("mouf",pi.name)
46 | self.assertEqual("/bla/mouf",pi.path)
47 | # check that some other info do change...
48 | self.assertEqual("me",pi.author)
49 |
50 |
51 | suite = unittest.TestSuite([
52 | unittest.TestLoader().loadTestsFromTestCase(PluginInfoTest),
53 | ])
54 |
--------------------------------------------------------------------------------
/package/test/test_SimpleMultiprocessPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | import unittest
4 | import os
5 |
6 | from yapsy.MultiprocessPluginManager import MultiprocessPluginManager
7 |
8 | class SimpleMultiprocessTestCase(unittest.TestCase):
9 | """
10 | Test the correct loading of a multiprocessed plugin as well as basic
11 | communication.
12 | """
13 |
14 | def setUp(self):
15 | """
16 | init
17 | """
18 | # create the plugin manager
19 | self.mpPluginManager = MultiprocessPluginManager(directories_list=[
20 | os.path.join(
21 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
22 | plugin_info_ext="multiprocess-plugin")
23 | # load the plugins that may be found
24 | self.mpPluginManager.collectPlugins()
25 | # Will be used later
26 | self.plugin_info = None
27 |
28 | def testUpAndRunning(self):
29 | """
30 | Test if the plugin is loaded and if the communication pipe is properly setuped.
31 | """
32 | for plugin_index, plugin in enumerate(self.mpPluginManager.getAllPlugins()):
33 | child_pipe = plugin.plugin_object.child_pipe
34 | content_from_parent = "hello-{0}-from-parent".format(plugin_index)
35 | child_pipe.send(content_from_parent)
36 | content_from_child = False
37 | if child_pipe.poll(5):
38 | content_from_child = child_pipe.recv()
39 | self.assertEqual("{0}|echo_from_child".format(content_from_parent),
40 | content_from_child)
41 | num_tested_plugin = plugin_index+1
42 | self.assertEqual(2, num_tested_plugin)
43 |
44 | suite = unittest.TestSuite([
45 | unittest.TestLoader().loadTestsFromTestCase(SimpleMultiprocessTestCase),
46 | ])
47 |
--------------------------------------------------------------------------------
/package/test/test_SimplePlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 | import unittest
5 | import os
6 |
7 | from yapsy.PluginManager import PluginManager
8 | from yapsy.IPlugin import IPlugin
9 | from yapsy.PluginFileLocator import PluginFileLocator
10 | from yapsy.PluginFileLocator import IPluginFileAnalyzer
11 | from yapsy import NormalizePluginNameForModuleName
12 | from configparser import ConfigParser
13 |
14 | class YapsyUtils(unittest.TestCase):
15 |
16 | def test_NormalizePluginNameForModuleName_on_ok_name(self):
17 | self.assertEqual("moufGlop2",NormalizePluginNameForModuleName("moufGlop2"))
18 |
19 | def test_NormalizePluginNameForModuleName_on_empty_name(self):
20 | self.assertEqual("_",NormalizePluginNameForModuleName(""))
21 |
22 | def test_NormalizePluginNameForModuleName_on_name_with_space(self):
23 | self.assertEqual("mouf_glop",NormalizePluginNameForModuleName("mouf glop"))
24 |
25 | def test_NormalizePluginNameForModuleName_on_name_with_nonalphanum(self):
26 | self.assertEqual("mouf__glop_a_é",NormalizePluginNameForModuleName("mouf+?glop:a/é"))
27 |
28 |
29 |
30 | class SimpleTestCase(unittest.TestCase):
31 | """
32 | Test the correct loading of a simple plugin as well as basic
33 | commands.
34 | """
35 |
36 | def setUp(self):
37 | """
38 | init
39 | """
40 | # create the plugin manager
41 | self.simplePluginManager = PluginManager(directories_list=[
42 | os.path.join(
43 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
44 | # load the plugins that may be found
45 | self.simplePluginManager.collectPlugins()
46 | # Will be used later
47 | self.plugin_info = None
48 |
49 | def plugin_loading_check(self):
50 | """
51 | Test if the correct plugin has been loaded.
52 | """
53 | if self.plugin_info is None:
54 | # check nb of categories
55 | self.assertEqual(len(self.simplePluginManager.getCategories()),1)
56 | sole_category = self.simplePluginManager.getCategories()[0]
57 | # check the number of plugins
58 | self.assertEqual(len(self.simplePluginManager.getPluginsOfCategory(sole_category)),1)
59 | self.plugin_info = self.simplePluginManager.getPluginsOfCategory(sole_category)[0]
60 | # test that the name of the plugin has been correctly defined
61 | self.assertEqual(self.plugin_info.name,"Simple Plugin")
62 | self.assertEqual(sole_category,self.plugin_info.category)
63 | else:
64 | self.assertTrue(True)
65 |
66 | def testLoaded(self):
67 | """
68 | Test if the correct plugin has been loaded.
69 | """
70 | self.plugin_loading_check()
71 |
72 | def testGetAll(self):
73 | """
74 | Test if the correct plugin has been loaded.
75 | """
76 | self.plugin_loading_check()
77 | self.assertEqual(len(self.simplePluginManager.getAllPlugins()),1)
78 | self.assertEqual(self.simplePluginManager.getAllPlugins()[0],self.plugin_info)
79 |
80 |
81 | def testActivationAndDeactivation(self):
82 | """
83 | Test if the activation procedure works.
84 | """
85 | self.plugin_loading_check()
86 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
87 | self.simplePluginManager.activatePluginByName(self.plugin_info.name,
88 | self.plugin_info.category)
89 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
90 | self.simplePluginManager.deactivatePluginByName(self.plugin_info.name,
91 | self.plugin_info.category)
92 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
93 |
94 |
95 | class SimplePluginAdvancedManipulationTestsCase(unittest.TestCase):
96 | """
97 | Test some advanced manipulation on the core data of a PluginManager.
98 | """
99 |
100 |
101 | def testCategoryManipulation(self):
102 | """
103 | Test querying, removing and adding plugins from/to a category.
104 | """
105 | spm = PluginManager(directories_list=[
106 | os.path.join(
107 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
108 | # load the plugins that may be found
109 | spm.collectPlugins()
110 | # check that the getCategories works
111 | self.assertEqual(len(spm.getCategories()),1)
112 | sole_category = spm.getCategories()[0]
113 | # check the getPluginsOfCategory
114 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1)
115 | plugin_info = spm.getPluginsOfCategory(sole_category)[0]
116 | # try to remove it and check that is worked
117 | spm.removePluginFromCategory(plugin_info,sole_category)
118 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0)
119 | # now re-add this plugin the to same category
120 | spm.appendPluginToCategory(plugin_info,sole_category)
121 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1)
122 |
123 |
124 | def testChangingCategoriesFilter(self):
125 | """
126 | Test the effect of setting a new category filer.
127 | """
128 | spm = PluginManager(directories_list=[
129 | os.path.join(
130 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
131 | # load the plugins that may be found
132 | spm.collectPlugins()
133 | newCategory = "Mouf"
134 | # Pre-requisite for the test
135 | previousCategories = spm.getCategories()
136 | self.assertTrue(len(previousCategories) >= 1)
137 | self.assertTrue(newCategory not in previousCategories)
138 | # change the category and see what's happening
139 | spm.setCategoriesFilter({newCategory: IPlugin})
140 | spm.collectPlugins()
141 | for categoryName in previousCategories:
142 | self.assertRaises(KeyError, spm.getPluginsOfCategory, categoryName)
143 | self.assertTrue(len(spm.getPluginsOfCategory(newCategory)) >= 1)
144 |
145 |
146 | def testCandidatesManipulation(self):
147 | """
148 | Test querying, removing and adding plugins from/to the lkist
149 | of plugins to load.
150 | """
151 | spm = PluginManager(directories_list=[
152 | os.path.join(
153 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
154 | # locate the plugins that should be loaded
155 | spm.locatePlugins()
156 | # check nb of candidatesx
157 | self.assertEqual(len(spm.getPluginCandidates()),1)
158 | # get the description of the plugin candidate
159 | candidate = spm.getPluginCandidates()[0]
160 | self.assertTrue(isinstance(candidate,tuple))
161 | # try removing the candidate
162 | spm.removePluginCandidate(candidate)
163 | self.assertEqual(len(spm.getPluginCandidates()),0)
164 | # try re-adding it
165 | spm.appendPluginCandidate(candidate)
166 | self.assertEqual(len(spm.getPluginCandidates()),1)
167 |
168 | def testTwoStepsLoad(self):
169 | """
170 | Test loading the plugins in two steps in order to collect more
171 | deltailed informations.
172 | """
173 | spm = PluginManager(directories_list=[
174 | os.path.join(
175 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
176 | # trigger the first step to look up for plugins
177 | spm.locatePlugins()
178 | # make full use of the "feedback" the loadPlugins can give
179 | # - set-up the callback function that will be called *before*
180 | # loading each plugin
181 | callback_infos = []
182 | def preload_cbk(plugin_info):
183 | callback_infos.append(plugin_info)
184 | callback_after_infos = []
185 | def postload_cbk(plugin_info):
186 | callback_after_infos.append(plugin_info)
187 | # - gather infos about the processed plugins (loaded or not)
188 | loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk)
189 | self.assertEqual(len(loadedPlugins),1)
190 | self.assertEqual(len(callback_infos),1)
191 | self.assertEqual(loadedPlugins[0].error,None)
192 | self.assertEqual(loadedPlugins[0],callback_infos[0])
193 | self.assertEqual(len(callback_after_infos),1)
194 | self.assertEqual(loadedPlugins[0],callback_infos[0])
195 | # check that the getCategories works
196 | self.assertEqual(len(spm.getCategories()),1)
197 | sole_category = spm.getCategories()[0]
198 | # check the getPluginsOfCategory
199 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1)
200 | plugin_info = spm.getPluginsOfCategory(sole_category)[0]
201 | # try to remove it and check that is worked
202 | spm.removePluginFromCategory(plugin_info,sole_category)
203 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0)
204 | # now re-add this plugin the to same category
205 | spm.appendPluginToCategory(plugin_info,sole_category)
206 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1)
207 |
208 | def testMultipleCategoriesForASamePlugin(self):
209 | """
210 | Test that associating a plugin to multiple categories works as expected.
211 | """
212 | class AnotherPluginIfce(object):
213 | def __init__(self):
214 | pass
215 | def activate(self):
216 | pass
217 | def deactivate(self):
218 | pass
219 |
220 | spm = PluginManager(
221 | categories_filter = {
222 | "Default": IPlugin,
223 | "IP": IPlugin,
224 | "Other": AnotherPluginIfce,
225 | },
226 | directories_list=[
227 | os.path.join(
228 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
229 | # load the plugins that may be found
230 | spm.collectPlugins()
231 | # check that the getCategories works
232 | self.assertEqual(len(spm.getCategories()),3)
233 | categories = spm.getCategories()
234 | self.assertTrue("Default" in categories)
235 | # check the getPluginsOfCategory
236 | self.assertEqual(len(spm.getPluginsOfCategory("Default")), 1)
237 | plugin_info = spm.getPluginsOfCategory("Default")[0]
238 | self.assertTrue("Default" in plugin_info.categories)
239 | self.assertTrue("IP" in plugin_info.categories)
240 | self.assertTrue("IP" in categories)
241 | # check the getPluginsOfCategory
242 | self.assertEqual(len(spm.getPluginsOfCategory("IP")),1)
243 | self.assertTrue("Other" in categories)
244 | # check the getPluginsOfCategory
245 | self.assertEqual(len(spm.getPluginsOfCategory("Other")),0)
246 | # try to remove the plugin from one category and check the
247 | # other category
248 | spm.removePluginFromCategory(plugin_info, "Default")
249 | self.assertEqual(len(spm.getPluginsOfCategory("Default")), 0)
250 | self.assertEqual(len(spm.getPluginsOfCategory("IP")), 1)
251 | # now re-add this plugin the to same category
252 | spm.appendPluginToCategory(plugin_info, "Default")
253 | self.assertEqual(len(spm.getPluginsOfCategory("Default")),1)
254 | self.assertEqual(len(spm.getPluginsOfCategory("IP")),1)
255 |
256 | def testGetPluginOf(self):
257 | """
258 | Test the plugin query function.
259 | """
260 | spm = PluginManager(
261 | categories_filter = {
262 | "Default": IPlugin,
263 | "IP": IPlugin,
264 | },
265 | directories_list=[
266 | os.path.join(
267 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
268 | # load the plugins that may be found
269 | spm.collectPlugins()
270 | # check the getPluginsOfCategory
271 | self.assertEqual(len(spm.getPluginsOf(categories="IP")), 1)
272 | self.assertEqual(len(spm.getPluginsOf(categories="Default")), 1)
273 | self.assertEqual(len(spm.getPluginsOf(name="Simple Plugin")), 1)
274 | self.assertEqual(len(spm.getPluginsOf(is_activated=False)), 1)
275 | self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=True)), 0)
276 | self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=False)), 1)
277 | self.assertEqual(len(spm.getPluginsOf(categories="IP", pouet=False)), 0)
278 | self.assertEqual(len(spm.getPluginsOf(categories=["IP"])), 0)
279 | # The order in the categories are added to plugin info is random in this setup, hence the strange formula below
280 | self.assertEqual(len(spm.getPluginsOf(categories=["IP", "Default"]) | spm.getPluginsOf(categories=["Default", "IP"])), 1)
281 | self.assertEqual(len(spm.getPluginsOf(category="Default") | spm.getPluginsOf(category="IP")), 1)
282 |
283 | class SimplePluginDetectionTestsCase(unittest.TestCase):
284 | """
285 | Test particular aspects of plugin detection
286 | """
287 |
288 | def testRecursivePluginlocation(self):
289 | """
290 | Test detection of plugins which by default must be
291 | recursive. Here we give the test directory as a plugin place
292 | whereas we expect the plugins to be in test/plugins.
293 | """
294 | spm = PluginManager(directories_list=[
295 | os.path.dirname(os.path.abspath(__file__))])
296 | # load the plugins that may be found
297 | spm.collectPlugins()
298 | # check that the getCategories works
299 | self.assertEqual(len(spm.getCategories()),1)
300 | sole_category = spm.getCategories()[0]
301 | # check the getPluginsOfCategory
302 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),2)
303 |
304 | def testDisablingRecursivePluginLocationIsEnforced(self):
305 | """
306 | Test detection of plugins when the detection is non recursive.
307 | Here we test that it cannot look into subdirectories of the
308 | test directory.
309 | """
310 | pluginLocator = PluginFileLocator()
311 | pluginLocator.setPluginPlaces([
312 | os.path.dirname(os.path.abspath(__file__))])
313 | pluginLocator.disableRecursiveScan()
314 | spm = PluginManager()
315 | spm.setPluginLocator(pluginLocator)
316 | # load the plugins that may be found
317 | spm.collectPlugins()
318 | # check that the getCategories works
319 | self.assertEqual(len(spm.getCategories()),1)
320 | sole_category = spm.getCategories()[0]
321 | # check the getPluginsOfCategory
322 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0)
323 |
324 |
325 | def testDisablingRecursivePluginLocationAllowsFindingTopLevelPlugins(self):
326 | """
327 | Test detection of plugins when the detection is non
328 | recursive. Here we test that if we give test/plugin as the
329 | directory to scan it can find the plugin.
330 | """
331 | pluginLocator = PluginFileLocator()
332 | pluginLocator.setPluginPlaces([
333 | os.path.join(
334 | os.path.dirname(os.path.abspath(__file__)),"plugins")])
335 | pluginLocator.disableRecursiveScan()
336 | spm = PluginManager()
337 | spm.setPluginLocator(pluginLocator)
338 | # load the plugins that may be found
339 | spm.collectPlugins()
340 | # check that the getCategories works
341 | self.assertEqual(len(spm.getCategories()),1)
342 | sole_category = spm.getCategories()[0]
343 | # check the getPluginsOfCategory
344 | self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1)
345 |
346 | def testEnforcingPluginDirsDoesNotKeepDefaultDir(self):
347 | """
348 | Test that providing the directories list override the default search directory
349 | instead of extending the default list.
350 | """
351 |
352 | class AcceptAllPluginFileAnalyzer(IPluginFileAnalyzer):
353 |
354 | def __init__(self):
355 | IPluginFileAnalyzer.__init__(self, "AcceptAll")
356 |
357 | def isValidPlugin(self, filename):
358 | return True
359 |
360 | def getInfosDictFromPlugin(self, dirpath, filename):
361 | return { "name": filename, "path": dirpath}, ConfigParser()
362 |
363 | pluginLocator = PluginFileLocator()
364 | pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()])
365 |
366 | spm_default_dirs = PluginManager(plugin_locator= pluginLocator)
367 | spm_default_dirs.locatePlugins()
368 | candidates_in_default_dir = spm_default_dirs.getPluginCandidates()
369 | candidates_files_in_default_dir = set([c[0] for c in candidates_in_default_dir])
370 |
371 | pluginLocator = PluginFileLocator()
372 | pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()])
373 | spm = PluginManager(plugin_locator= pluginLocator,
374 | directories_list=[os.path.dirname(os.path.abspath(__file__)),"does-not-exists"])
375 | spm.locatePlugins()
376 | candidates = spm.getPluginCandidates()
377 | candidates_files = set([c[0] for c in candidates])
378 |
379 | self.assertFalse(set(candidates_files_in_default_dir).issubset(set(candidates_files)))
380 |
381 | suite = unittest.TestSuite([
382 | unittest.TestLoader().loadTestsFromTestCase(YapsyUtils),
383 | unittest.TestLoader().loadTestsFromTestCase(SimpleTestCase),
384 | unittest.TestLoader().loadTestsFromTestCase(SimplePluginAdvancedManipulationTestsCase),
385 | unittest.TestLoader().loadTestsFromTestCase(SimplePluginDetectionTestsCase),
386 | ])
387 |
--------------------------------------------------------------------------------
/package/test/test_Singleton.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 |
5 | import os
6 | import unittest
7 |
8 | from yapsy.ConfigurablePluginManager import ConfigurablePluginManager
9 | from yapsy.VersionedPluginManager import VersionedPluginManager
10 | from yapsy.PluginManager import PluginManagerSingleton
11 | from configparser import ConfigParser
12 |
13 |
14 | """
15 | There can be only one series of tests for the singleton, guess why ...
16 | """
17 |
18 | class ConfigSingletonTestsCase(unittest.TestCase):
19 | """
20 | Test the correct loading of a simple plugin as well as basic
21 | commands, use the Singleton version of the ConfigurablePluginManager.
22 | """
23 |
24 | CONFIG_FILE = test_settings.TEMP_CONFIG_FILE_NAME
25 |
26 | def setUp(self):
27 | """
28 | init
29 | """
30 | # create a config file
31 | self.config_file = self.CONFIG_FILE
32 | self.config_parser = ConfigParser()
33 | self.plugin_info = None
34 |
35 | # create the plugin manager
36 | PluginManagerSingleton.setBehaviour([ConfigurablePluginManager,VersionedPluginManager])
37 | pluginManager = PluginManagerSingleton.get()
38 | pluginManager.setPluginPlaces(directories_list=[os.path.dirname(os.path.abspath(__file__))])
39 | pluginManager.setPluginInfoExtension("yapsy-config-plugin")
40 | pluginManager.setConfigParser(self.config_parser,self.update_config)
41 | # load the plugins that may be found
42 | pluginManager.collectPlugins()
43 |
44 | def tearDown(self):
45 | """
46 | When the test has been performed erase the temp file.
47 | """
48 | if os.path.isfile(self.config_file):
49 | os.remove(self.config_file)
50 |
51 |
52 | def testConfigurationFileExistence(self):
53 | """
54 | Test if the configuration file has been properly written.
55 | """
56 | # activate the only loaded plugin
57 | self.plugin_activate()
58 | # get rid of the plugin manager and create a new one
59 | self.config_parser.read(self.config_file)
60 | self.assertTrue(self.config_parser.has_section("Plugin Management"))
61 | self.assertTrue(self.config_parser.has_option("Plugin Management",
62 | "default_plugins_to_load"))
63 |
64 |
65 | def testLoaded(self):
66 | """
67 | Test if the correct plugin has been loaded.
68 | """
69 | self.plugin_loading_check()
70 |
71 | def testActivationAndDeactivation(self):
72 | """
73 | Test if the activation/deactivaion procedures work.
74 | """
75 | self.plugin_activate()
76 | PluginManagerSingleton.get().deactivatePluginByName(self.plugin_info.name,
77 | self.plugin_info.category)
78 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
79 |
80 | def testPluginOptions(self):
81 | """
82 | Test is the plugin can register and access options from the
83 | ConfigParser.
84 | """
85 | self.plugin_activate()
86 | plugin = self.plugin_info.plugin_object
87 | plugin.choseTestOption("voila")
88 | self.assertTrue(plugin.checkTestOption())
89 | self.assertEqual(plugin.getTestOption(),"voila")
90 |
91 |
92 | #--- UTILITIES
93 |
94 | def plugin_loading_check(self):
95 | """
96 | Test if the correct plugin has been loaded.
97 | """
98 | if self.plugin_info is None:
99 | pluginManager = PluginManagerSingleton.get()
100 | # check nb of categories
101 | self.assertEqual(len(pluginManager.getCategories()),1)
102 | sole_category = pluginManager.getCategories()[0]
103 | # check the number of plugins
104 | self.assertEqual(len(pluginManager.getPluginsOfCategory(sole_category)),1)
105 | self.plugin_info = pluginManager.getPluginsOfCategory(sole_category)[0]
106 | # test that the name of the plugin has been correctly defined
107 | self.assertEqual(self.plugin_info.name,"Config Plugin")
108 | self.assertEqual(sole_category,self.plugin_info.category)
109 | else:
110 | self.assertTrue(True)
111 |
112 | def plugin_activate(self):
113 | """
114 | Activate the plugin with basic checking
115 | """
116 | self.plugin_loading_check()
117 | if not self.plugin_info.plugin_object.is_activated:
118 | PluginManagerSingleton.get().activatePluginByName(self.plugin_info.name,
119 | self.plugin_info.category)
120 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
121 |
122 |
123 | def update_config(self):
124 | """
125 | Write the content of the ConfigParser in a file.
126 | """
127 | cf = open(self.config_file,"a")
128 | self.config_parser.write(cf)
129 | cf.close()
130 |
131 |
132 |
133 | suite = unittest.TestSuite([
134 | unittest.TestLoader().loadTestsFromTestCase(ConfigSingletonTestsCase),
135 | ])
136 |
--------------------------------------------------------------------------------
/package/test/test_VersionedPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | from . import test_settings
4 | from .test_settings import TEST_MESSAGE
5 | import unittest
6 | import os
7 |
8 | from yapsy.IPlugin import IPlugin
9 | from yapsy.VersionedPluginManager import VersionedPluginManager
10 |
11 |
12 | class VersionedTestsCase(unittest.TestCase):
13 | """
14 | Test the correct loading of a simple plugin as well as basic
15 | commands.
16 | """
17 |
18 | def setUp(self):
19 | """
20 | init
21 | """
22 | # create the plugin manager
23 | self.versionedPluginManager = VersionedPluginManager(
24 | directories_list=[os.path.join(
25 | os.path.dirname(os.path.abspath(__file__)),"plugins")],
26 | plugin_info_ext="version-plugin",
27 | )
28 | # load the plugins that may be found
29 | self.versionedPluginManager.collectPlugins()
30 | # Will be used later
31 | self.plugin_info = None
32 |
33 | def plugin_loading_check(self):
34 | """
35 | Test if the correct plugin has been loaded.
36 | """
37 | if self.plugin_info is None:
38 | # check nb of categories
39 | self.assertEqual(len(self.versionedPluginManager.getCategories()),1)
40 | sole_category = self.versionedPluginManager.getCategories()[0]
41 | # check the number of plugins (the older versions of the
42 | # plugins should not be there)
43 | self.assertEqual(len(self.versionedPluginManager.getPluginsOfCategory(sole_category)),1)
44 | # older versions of the plugin should be found in the attic
45 | self.assertEqual(len(self.versionedPluginManager.getPluginsOfCategoryFromAttic(sole_category)),4)
46 | plugins = self.versionedPluginManager.getPluginsOfCategory(sole_category)
47 | self.plugin_info = None
48 | for plugin_info in plugins:
49 | TEST_MESSAGE("plugin info: %s" % plugin_info)
50 | if plugin_info.name == "Versioned Plugin":
51 | self.plugin_info = plugin_info
52 | break
53 | self.assertTrue(self.plugin_info)
54 | # test that the name of the plugin has been correctly defined
55 | self.assertEqual(self.plugin_info.name,"Versioned Plugin")
56 | self.assertEqual(sole_category,self.plugin_info.category)
57 | else:
58 | self.assertTrue(True)
59 |
60 | def testLoaded(self):
61 | """
62 | Test if the correct plugin has been loaded.
63 | """
64 | self.plugin_loading_check()
65 | sole_category = self.versionedPluginManager.getCategories()[0]
66 | self.assertEqual(len(self.versionedPluginManager.getLatestPluginsOfCategory(sole_category)),1)
67 | self.plugin_info = self.versionedPluginManager.getLatestPluginsOfCategory(sole_category)[0]
68 | TEST_MESSAGE("plugin info: %s" % self.plugin_info)
69 | # test that the name of the plugin has been correctly defined
70 | self.assertEqual(self.plugin_info.name,"Versioned Plugin")
71 | self.assertEqual(sole_category,self.plugin_info.category)
72 | self.assertEqual("1.2",str(self.plugin_info.version))
73 |
74 |
75 | def testLatestPluginOfCategory(self):
76 | self.plugin_loading_check()
77 |
78 | def testActivationAndDeactivation(self):
79 | """
80 | Test if the activation procedure works.
81 | """
82 | self.plugin_loading_check()
83 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
84 | self.versionedPluginManager.activatePluginByName(self.plugin_info.name,
85 | self.plugin_info.category)
86 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
87 | self.versionedPluginManager.deactivatePluginByName(self.plugin_info.name,
88 | self.plugin_info.category)
89 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
90 | # also check that this is the plugin of the latest version
91 | # that has been activated (ok the following test is already
92 | # ensured by the plugin_loading_check method, but this is to
93 | # make the things clear: the plugin chosen for activation is
94 | # the one with the latest version)
95 | self.assertEqual("1.2",str(self.plugin_info.version))
96 |
97 |
98 | def testDirectActivationAndDeactivation(self):
99 | """
100 | Test if the activation procedure works when directly activating a plugin.
101 | """
102 | self.plugin_loading_check()
103 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
104 | TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object)
105 | self.plugin_info.plugin_object.activate()
106 | self.assertTrue(self.plugin_info.plugin_object.is_activated)
107 | self.plugin_info.plugin_object.deactivate()
108 | self.assertTrue(not self.plugin_info.plugin_object.is_activated)
109 |
110 |
111 | def testAtticConsistencyAfterCategoryFilterUpdate(self):
112 | """
113 | Test that changing the category filer doesn't make the attic inconsistent.
114 | """
115 | self.plugin_loading_check()
116 | newCategory = "Mouf"
117 | # Pre-requisite for the test
118 | previousCategories = self.versionedPluginManager.getCategories()
119 | self.assertTrue(len(previousCategories) >= 1)
120 | self.assertTrue(newCategory not in previousCategories)
121 | # change the category and see what's happening
122 | self.versionedPluginManager.setCategoriesFilter({newCategory: IPlugin})
123 | self.versionedPluginManager.collectPlugins()
124 | for categoryName in previousCategories:
125 | self.assertRaises(KeyError, self.versionedPluginManager\
126 | .getPluginsOfCategory, categoryName)
127 | self.assertEqual(len(self.versionedPluginManager\
128 | .getPluginsOfCategoryFromAttic(newCategory)),4)
129 |
130 |
131 | suite = unittest.TestSuite([
132 | unittest.TestLoader().loadTestsFromTestCase(VersionedTestsCase),
133 | ])
134 |
--------------------------------------------------------------------------------
/package/test/test_settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | import os
4 | import sys
5 |
6 | import logging
7 | TEST_MESSAGE = logging.debug
8 |
9 | TEMP_CONFIG_FILE_NAME=os.path.join(
10 | os.path.dirname(
11 | os.path.abspath(__file__)),
12 | "tempconfig")
13 |
14 | # set correct loading path for yapsy's files
15 | sys.path.insert(0,
16 | os.path.dirname(
17 | os.path.dirname(
18 | os.path.abspath(__file__))))
19 |
20 | sys.path.insert(0,
21 | os.path.dirname(
22 | os.path.dirname(
23 | os.path.dirname(
24 | os.path.abspath(__file__)))))
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/package/yapsy/AutoInstallPluginManager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | Role
5 | ====
6 |
7 | Defines plugin managers that can handle the installation of plugin
8 | files into the right place. Then the end-user does not have to browse
9 | to the plugin directory to install them.
10 |
11 | API
12 | ===
13 | """
14 |
15 | import os
16 | import shutil
17 | import zipfile
18 |
19 | from yapsy.IPlugin import IPlugin
20 | from yapsy.PluginManagerDecorator import PluginManagerDecorator
21 | from yapsy import log
22 | from io import StringIO
23 |
24 |
25 | class AutoInstallPluginManager(PluginManagerDecorator):
26 | """
27 | A plugin manager that also manages the installation of the plugin
28 | files into the appropriate directory.
29 |
30 | Ctor Arguments:
31 |
32 | ``plugin_install_dir``
33 | The directory where new plugins to be installed will be copied.
34 |
35 | .. warning:: If ``plugin_install_dir`` does not correspond to
36 | an element of the ``directories_list``, it is
37 | appended to the later.
38 | """
39 |
40 |
41 | def __init__(self,
42 | plugin_install_dir=None,
43 | decorated_manager=None,
44 | # The following args will only be used if we need to
45 | # create a default PluginManager
46 | categories_filter=None,
47 | directories_list=None,
48 | plugin_info_ext="yapsy-plugin"):
49 | if categories_filter is None:
50 | categories_filter = {"Default":IPlugin}
51 | # Create the base decorator class
52 | PluginManagerDecorator.__init__(self,
53 | decorated_manager,
54 | categories_filter,
55 | directories_list,
56 | plugin_info_ext)
57 | # set the directory for new plugins
58 | self.plugins_places=[]
59 | self.setInstallDir(plugin_install_dir)
60 |
61 | def setInstallDir(self,plugin_install_dir):
62 | """
63 | Set the directory where to install new plugins.
64 | """
65 | if not (plugin_install_dir in self.plugins_places):
66 | self.plugins_places.append(plugin_install_dir)
67 | self.install_dir = plugin_install_dir
68 |
69 | def getInstallDir(self):
70 | """
71 | Return the directory where new plugins should be installed.
72 | """
73 | return self.install_dir
74 |
75 | def install(self, directory, plugin_info_filename):
76 | """
77 | Giving the plugin's info file (e.g. ``myplugin.yapsy-plugin``),
78 | and the directory where it is located, get all the files that
79 | define the plugin and copy them into the correct directory.
80 |
81 | Return ``True`` if the installation is a success, ``False`` if
82 | it is a failure.
83 | """
84 | # start collecting essential info about the new plugin
85 | plugin_info, config_parser = self._gatherCorePluginInfo(directory, plugin_info_filename)
86 | # now determine the path of the file to execute,
87 | # depending on wether the path indicated is a
88 | # directory or a file
89 | if not (os.path.exists(plugin_info.path) or os.path.exists(plugin_info.path+".py") ):
90 | log.warning("Could not find the plugin's implementation for %s." % plugin_info.name)
91 | return False
92 | if os.path.isdir(plugin_info.path):
93 | try:
94 | shutil.copytree(plugin_info.path,
95 | os.path.join(self.install_dir,os.path.basename(plugin_info.path)))
96 | shutil.copy(os.path.join(directory, plugin_info_filename),
97 | self.install_dir)
98 | except:
99 | log.error("Could not install plugin: %s." % plugin_info.name)
100 | return False
101 | else:
102 | return True
103 | elif os.path.isfile(plugin_info.path+".py"):
104 | try:
105 | shutil.copy(plugin_info.path+".py",
106 | self.install_dir)
107 | shutil.copy(os.path.join(directory, plugin_info_filename),
108 | self.install_dir)
109 | except:
110 | log.error("Could not install plugin: %s." % plugin_info.name)
111 | return False
112 | else:
113 | return True
114 | else:
115 | return False
116 |
117 |
118 | def installFromZIP(self, plugin_ZIP_filename):
119 | """
120 | Giving the plugin's zip file (e.g. ``myplugin.zip``), check
121 | that their is a valid info file in it and correct all the
122 | plugin files into the correct directory.
123 |
124 | .. warning:: Only available for python 2.6 and later.
125 |
126 | Return ``True`` if the installation is a success, ``False`` if
127 | it is a failure.
128 | """
129 | if not os.path.isfile(plugin_ZIP_filename):
130 | log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename)
131 | return False
132 | try:
133 | candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename)
134 | first_bad_file = candidateZipFile.testzip()
135 | if first_bad_file:
136 | raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file)
137 | except Exception as e:
138 | log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename,e))
139 | return False
140 | zipContent = candidateZipFile.namelist()
141 | log.info("Investigating the content of a zip file containing: '%s'" % zipContent)
142 | log.info("Sanity checks on zip's contained files (looking for hazardous path symbols).")
143 | # check absence of root path and ".." shortcut that would
144 | # send the file oustide the desired directory
145 | for containedFileName in zipContent:
146 | # WARNING: the sanity checks below are certainly not
147 | # exhaustive (maybe we could do something a bit smarter by
148 | # using os.path.expanduser, os.path.expandvars and
149 | # os.path.normpath)
150 | if containedFileName.startswith("/"):
151 | log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with '/'" % containedFileName)
152 | return False
153 | if containedFileName.startswith(r"\\") or containedFileName.startswith("//"):
154 | log.warning(r"Unsecure zip file, rejected because one of its file paths ('%s') starts with '\\'" % containedFileName)
155 | return False
156 | if os.path.splitdrive(containedFileName)[0]:
157 | log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with a drive letter" % containedFileName)
158 | return False
159 | if os.path.isabs(containedFileName):
160 | log.warning("Unsecure zip file, rejected because one of its file paths ('%s') is absolute" % containedFileName)
161 | return False
162 | pathComponent = os.path.split(containedFileName)
163 | if ".." in pathComponent:
164 | log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '..'" % containedFileName)
165 | return False
166 | if "~" in pathComponent:
167 | log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '~'" % containedFileName)
168 | return False
169 | infoFileCandidates = [filename for filename in zipContent if os.path.dirname(filename)==""]
170 | if not infoFileCandidates:
171 | log.warning("Zip file structure seems wrong in '%s', no info file found." % plugin_ZIP_filename)
172 | return False
173 | isValid = False
174 | log.info("Looking for the zipped plugin's info file among '%s'" % infoFileCandidates)
175 | for infoFileName in infoFileCandidates:
176 | infoFile = candidateZipFile.read(infoFileName)
177 | log.info("Assuming the zipped plugin info file to be '%s'" % infoFileName)
178 | pluginName,moduleName,_ = self._getPluginNameAndModuleFromStream(StringIO(str(infoFile,encoding="utf-8")))
179 | if moduleName is None:
180 | continue
181 | log.info("Checking existence of the expected module '%s' in the zip file" % moduleName)
182 | candidate_module_paths = [
183 | moduleName,
184 | # Try path consistent with the platform specific one
185 | os.path.join(moduleName,"__init__.py"),
186 | # Try typical paths (unix and windows)
187 | "%s/__init__.py" % moduleName,
188 | "%s\\__init__.py" % moduleName
189 | ]
190 | for candidate in candidate_module_paths:
191 | if candidate in zipContent:
192 | isValid = True
193 | break
194 | if isValid:
195 | break
196 | if not isValid:
197 | log.warning("Zip file structure seems wrong in '%s', "
198 | "could not match info file with the implementation of plugin '%s'." % (plugin_ZIP_filename,pluginName))
199 | return False
200 | else:
201 | try:
202 | candidateZipFile.extractall(self.install_dir)
203 | return True
204 | except Exception as e:
205 | log.error("Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName,plugin_ZIP_filename,e))
206 | return False
207 |
208 |
--------------------------------------------------------------------------------
/package/yapsy/ConfigurablePluginManager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | Role
5 | ====
6 |
7 | Defines plugin managers that can handle configuration files similar to
8 | the ini files manipulated by Python's ConfigParser module.
9 |
10 | API
11 | ===
12 | """
13 |
14 | from yapsy.IPlugin import IPlugin
15 |
16 |
17 | from yapsy.PluginManagerDecorator import PluginManagerDecorator
18 | from yapsy.PluginManager import PLUGIN_NAME_FORBIDEN_STRING
19 |
20 |
21 | class ConfigurablePluginManager(PluginManagerDecorator):
22 | """
23 | A plugin manager that also manages a configuration file.
24 |
25 | The configuration file will be accessed through a ``ConfigParser``
26 | derivated object. The file can be used for other purpose by the
27 | application using this plugin manager as it will only add a new
28 | specific section ``[Plugin Management]`` for itself and also new
29 | sections for some plugins that will start with ``[Plugin:...]``
30 | (only the plugins that explicitly requires to save configuration
31 | options will have this kind of section).
32 |
33 | .. warning:: when giving/building the list of plugins to activate
34 | by default, there must not be any space in the list
35 | (neither in the names nor in between)
36 |
37 | The ``config_change_trigger`` argument can be used to set a
38 | specific method to call when the configuration is
39 | altered. This will let the client application manage the way
40 | they want the configuration to be updated (e.g. write on file
41 | at each change or at precise time intervalls or whatever....)
42 |
43 | .. warning:: when no ``config_change_trigger`` is given and if
44 | the provided ``configparser_instance`` doesn't handle it
45 | implicitely, recording the changes persistently (ie writing on
46 | the config file) won't happen.
47 | """
48 |
49 | CONFIG_SECTION_NAME = "Plugin Management"
50 |
51 |
52 | def __init__(self,
53 | configparser_instance=None,
54 | config_change_trigger= lambda :True,
55 | decorated_manager=None,
56 | # The following args will only be used if we need to
57 | # create a default PluginManager
58 | categories_filter=None,
59 | directories_list=None,
60 | plugin_info_ext="yapsy-plugin"):
61 | if categories_filter is None:
62 | categories_filter = {"Default":IPlugin}
63 | # Create the base decorator class
64 | PluginManagerDecorator.__init__(self,decorated_manager,
65 | categories_filter,
66 | directories_list,
67 | plugin_info_ext)
68 | self.setConfigParser(configparser_instance, config_change_trigger)
69 |
70 |
71 | def setConfigParser(self,configparser_instance,config_change_trigger):
72 | """
73 | Set the ConfigParser instance.
74 | """
75 | self.config_parser = configparser_instance
76 | # set the (optional) fucntion to be called when the
77 | # configuration is changed:
78 | self.config_has_changed = config_change_trigger
79 |
80 | def __getCategoryPluginsListFromConfig(self, plugin_list_str):
81 | """
82 | Parse the string describing the list of plugins to activate,
83 | to discover their actual names and return them.
84 | """
85 | return plugin_list_str.strip(" ").split("%s"%PLUGIN_NAME_FORBIDEN_STRING)
86 |
87 | def __getCategoryPluginsConfigFromList(self, plugin_list):
88 | """
89 | Compose a string describing the list of plugins to activate
90 | """
91 | return PLUGIN_NAME_FORBIDEN_STRING.join(plugin_list)
92 |
93 | def __getCategoryOptionsName(self,category_name):
94 | """
95 | Return the appropirately formated version of the category's
96 | option.
97 | """
98 | return "%s_plugins_to_load" % category_name.replace(" ","_")
99 |
100 | def __addPluginToConfig(self,category_name, plugin_name):
101 | """
102 | Utility function to add a plugin to the list of plugin to be
103 | activated.
104 | """
105 | # check that the section is here
106 | if not self.config_parser.has_section(self.CONFIG_SECTION_NAME):
107 | self.config_parser.add_section(self.CONFIG_SECTION_NAME)
108 | # check that the category's list of activated plugins is here too
109 | option_name = self.__getCategoryOptionsName(category_name)
110 | if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name):
111 | # if there is no list yet add a new one
112 | self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,plugin_name)
113 | return self.config_has_changed()
114 | else:
115 | # get the already existing list and append the new
116 | # activated plugin to it.
117 | past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name)
118 | past_list = self.__getCategoryPluginsListFromConfig(past_list_str)
119 | # make sure we don't add it twice
120 | if plugin_name not in past_list:
121 | past_list.append(plugin_name)
122 | new_list_str = self.__getCategoryPluginsConfigFromList(past_list)
123 | self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str)
124 | return self.config_has_changed()
125 |
126 | def __removePluginFromConfig(self,category_name, plugin_name):
127 | """
128 | Utility function to add a plugin to the list of plugin to be
129 | activated.
130 | """
131 | # check that the section is here
132 | if not self.config_parser.has_section(self.CONFIG_SECTION_NAME):
133 | # then nothing to remove :)
134 | return
135 | # check that the category's list of activated plugins is here too
136 | option_name = self.__getCategoryOptionsName(category_name)
137 | if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name):
138 | # if there is no list still nothing to do
139 | return
140 | else:
141 | # get the already existing list
142 | past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name)
143 | past_list = self.__getCategoryPluginsListFromConfig(past_list_str)
144 | if plugin_name in past_list:
145 | past_list.remove(plugin_name)
146 | new_list_str = self.__getCategoryPluginsConfigFromList(past_list)
147 | self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str)
148 | self.config_has_changed()
149 |
150 |
151 |
152 | def registerOptionFromPlugin(self,
153 | category_name, plugin_name,
154 | option_name, option_value):
155 | """
156 | To be called from a plugin object, register a given option in
157 | the name of a given plugin.
158 | """
159 | section_name = "%s Plugin: %s" % (category_name,plugin_name)
160 | # if the plugin's section is not here yet, create it
161 | if not self.config_parser.has_section(section_name):
162 | self.config_parser.add_section(section_name)
163 | # set the required option
164 | self.config_parser.set(section_name,option_name,option_value)
165 | self.config_has_changed()
166 |
167 | def hasOptionFromPlugin(self,
168 | category_name, plugin_name, option_name):
169 | """
170 | To be called from a plugin object, return True if the option
171 | has already been registered.
172 | """
173 | section_name = "%s Plugin: %s" % (category_name,plugin_name)
174 | return self.config_parser.has_section(section_name) and self.config_parser.has_option(section_name,option_name)
175 |
176 | def readOptionFromPlugin(self,
177 | category_name, plugin_name, option_name):
178 | """
179 | To be called from a plugin object, read a given option in
180 | the name of a given plugin.
181 | """
182 | section_name = "%s Plugin: %s" % (category_name,plugin_name)
183 | return self.config_parser.get(section_name,option_name)
184 |
185 |
186 | def __decoratePluginObject(self, category_name, plugin_name, plugin_object):
187 | """
188 | Add two methods to the plugin objects that will make it
189 | possible for it to benefit from this class's api concerning
190 | the management of the options.
191 | """
192 | plugin_object.setConfigOption = lambda x,y: self.registerOptionFromPlugin(category_name,
193 | plugin_name,
194 | x,y)
195 | plugin_object.setConfigOption.__doc__ = self.registerOptionFromPlugin.__doc__
196 | plugin_object.getConfigOption = lambda x: self.readOptionFromPlugin(category_name,
197 | plugin_name,
198 | x)
199 | plugin_object.getConfigOption.__doc__ = self.readOptionFromPlugin.__doc__
200 | plugin_object.hasConfigOption = lambda x: self.hasOptionFromPlugin(category_name,
201 | plugin_name,
202 | x)
203 | plugin_object.hasConfigOption.__doc__ = self.hasOptionFromPlugin.__doc__
204 |
205 | def activatePluginByName(self, plugin_name, category_name="Default", save_state=True):
206 | """
207 | Activate a plugin, , and remember it (in the config file).
208 |
209 | If you want the plugin to benefit from the configuration
210 | utility defined by this manager, it is crucial to use this
211 | method to activate a plugin and not call the plugin object's
212 | ``activate`` method. In fact, this method will also "decorate"
213 | the plugin object so that it can use this class's methods to
214 | register its own options.
215 |
216 | By default, the plugin's activation is registered in the
217 | config file but if you d'ont want this set the 'save_state'
218 | argument to False.
219 | """
220 | # first decorate the plugin
221 | pta = self._component.getPluginByName(plugin_name,category_name)
222 | if pta is None:
223 | return None
224 | self.__decoratePluginObject(category_name,plugin_name,pta.plugin_object)
225 | # activate the plugin
226 | plugin_object = self._component.activatePluginByName(plugin_name,category_name)
227 | # check the activation and then optionally set the config option
228 | if plugin_object.is_activated:
229 | if save_state:
230 | self.__addPluginToConfig(category_name,plugin_name)
231 | return plugin_object
232 | return None
233 |
234 | def deactivatePluginByName(self, plugin_name, category_name="Default", save_state=True):
235 | """
236 | Deactivate a plugin, and remember it (in the config file).
237 |
238 | By default, the plugin's deactivation is registered in the
239 | config file but if you d'ont want this set the ``save_state``
240 | argument to False.
241 | """
242 | # activate the plugin
243 | plugin_object = self._component.deactivatePluginByName(plugin_name,category_name)
244 | if plugin_object is None:
245 | return None
246 | # check the deactivation and then optionnally set the config option
247 | if not plugin_object.is_activated:
248 | if save_state:
249 | self.__removePluginFromConfig(category_name,plugin_name)
250 | return plugin_object
251 | return None
252 |
253 | def loadPlugins(self,callback=None, callback_after=None):
254 | """
255 | Walk through the plugins' places and look for plugins. Then
256 | for each plugin candidate look for its category, load it and
257 | stores it in the appropriate slot of the ``category_mapping``.
258 | """
259 | self._component.loadPlugins(callback, callback_after)
260 | # now load the plugins according to the recorded configuration
261 | if self.config_parser.has_section(self.CONFIG_SECTION_NAME):
262 | # browse all the categories
263 | for category_name in list(self._component.category_mapping.keys()):
264 | # get the list of plugins to be activated for this
265 | # category
266 | option_name = "%s_plugins_to_load"%category_name
267 | if self.config_parser.has_option(self.CONFIG_SECTION_NAME,
268 | option_name):
269 | plugin_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,
270 | option_name)
271 | plugin_list = self.__getCategoryPluginsListFromConfig(plugin_list_str)
272 | # activate all the plugins that should be
273 | # activated
274 | for plugin_name in plugin_list:
275 | self.activatePluginByName(plugin_name,category_name)
276 |
277 |
278 |
279 |
280 |
--------------------------------------------------------------------------------
/package/yapsy/FilteredPluginManager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | Role
5 | ====
6 |
7 | Defines the basic mechanisms to have a plugin manager filter the
8 | available list of plugins after locating them and before loading them.
9 |
10 | One use fo this would be to prevent untrusted plugins from entering
11 | the system.
12 |
13 | To use it properly you must reimplement or monkey patch the
14 | ``IsPluginOk`` method, as in the following example::
15 |
16 | # define a plugin manager (with you prefered options)
17 | pm = PluginManager(...)
18 | # decorate it with the Filtering mechanics
19 | pm = FilteredPluginManager(pm)
20 | # define a custom predicate that filters out plugins without descriptions
21 | pm.isPluginOk = lambda x: x.description!=""
22 |
23 |
24 | API
25 | ===
26 | """
27 |
28 |
29 | from yapsy.IPlugin import IPlugin
30 | from yapsy.PluginManagerDecorator import PluginManagerDecorator
31 |
32 |
33 | class FilteredPluginManager(PluginManagerDecorator):
34 | """
35 | Base class for decorators which filter the plugins list
36 | before they are loaded.
37 | """
38 |
39 | def __init__(self,
40 | decorated_manager=None,
41 | categories_filter=None,
42 | directories_list=None,
43 | plugin_info_ext="yapsy-plugin"):
44 | if categories_filter is None:
45 | categories_filter = {"Default":IPlugin}
46 | # Create the base decorator class
47 | PluginManagerDecorator.__init__(self,decorated_manager,
48 | categories_filter,
49 | directories_list,
50 | plugin_info_ext)
51 | # prepare the mapping of the latest version of each plugin
52 | self.rejectedPlugins = [ ]
53 |
54 |
55 |
56 | def filterPlugins(self):
57 | """
58 | Go through the currently available candidates, and and either
59 | leaves them, or moves them into the list of rejected Plugins.
60 |
61 | Can be overridden if overriding ``isPluginOk`` sentinel is not
62 | powerful enough.
63 | """
64 | self.rejectedPlugins = [ ]
65 | for candidate_infofile, candidate_filepath, plugin_info in self._component.getPluginCandidates():
66 | if not self.isPluginOk( plugin_info):
67 | self.rejectPluginCandidate((candidate_infofile, candidate_filepath, plugin_info) )
68 |
69 | def rejectPluginCandidate(self,pluginTuple):
70 | """
71 | Move a plugin from the candidates list to the rejected List.
72 | """
73 | if pluginTuple in self.getPluginCandidates():
74 | self._component.removePluginCandidate(pluginTuple)
75 | if not pluginTuple in self.rejectedPlugins:
76 | self.rejectedPlugins.append(pluginTuple)
77 |
78 | def unrejectPluginCandidate(self,pluginTuple):
79 | """
80 | Move a plugin from the rejected list to into the candidates
81 | list.
82 | """
83 | if not pluginTuple in self.getPluginCandidates():
84 | self._component.appendPluginCandidate(pluginTuple)
85 | if pluginTuple in self.rejectedPlugins:
86 | self.rejectedPlugins.remove(pluginTuple)
87 |
88 | def removePluginCandidate(self,pluginTuple):
89 | """
90 | Remove a plugin from the list of candidates.
91 | """
92 | if pluginTuple in self.getPluginCandidates():
93 | self._component.removePluginCandidate(pluginTuple)
94 | if pluginTuple in self.rejectedPlugins:
95 | self.rejectedPlugins.remove(pluginTuple)
96 |
97 |
98 | def appendPluginCandidate(self,pluginTuple):
99 | """
100 | Add a new candidate.
101 | """
102 | if self.isPluginOk(pluginTuple[2]):
103 | if pluginTuple not in self.getPluginCandidates():
104 | self._component.appendPluginCandidate(pluginTuple)
105 | else:
106 | if not pluginTuple in self.rejectedPlugins:
107 | self.rejectedPlugins.append(pluginTuple)
108 |
109 | def isPluginOk(self,info):
110 | """
111 | Sentinel function to detect if a plugin should be filtered.
112 |
113 | ``info`` is an instance of a ``PluginInfo`` and this method is
114 | expected to return True if the corresponding plugin can be
115 | accepted, and False if it must be filtered out.
116 |
117 | Subclasses should override this function and return false for
118 | any plugin which they do not want to be loadable.
119 | """
120 | return True
121 |
122 | def locatePlugins(self):
123 | """
124 | locate and filter plugins.
125 | """
126 | #Reset Catalogue
127 | self.setCategoriesFilter(self._component.categories_interfaces)
128 | #Reread and filter.
129 | self._component.locatePlugins()
130 | self.filterPlugins()
131 | return len(self._component.getPluginCandidates())
132 |
133 | def getRejectedPlugins(self):
134 | """
135 | Return the list of rejected plugins.
136 | """
137 | return self.rejectedPlugins[:]
138 |
--------------------------------------------------------------------------------
/package/yapsy/IMultiprocessChildPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | Originally defined the basic interfaces for multiprocessed plugins.
9 |
10 | Deprecation Note
11 | ================
12 |
13 | This class is deprecated and replaced by :doc:`IMultiprocessChildPlugin`.
14 |
15 | Child classes of `IMultiprocessChildPlugin` used to be an `IPlugin` as well as
16 | a `multiprocessing.Process`, possibly playing with the functionalities of both,
17 | which make maintenance harder than necessary.
18 |
19 | And indeed following a bug fix to make multiprocess plugins work on Windows,
20 | instances of IMultiprocessChildPlugin inherit Process but are not exactly the
21 | running process (there is a new wrapper process).
22 |
23 | API
24 | ===
25 | """
26 |
27 | from multiprocessing import Process
28 | from yapsy.IMultiprocessPlugin import IMultiprocessPlugin
29 |
30 |
31 | class IMultiprocessChildPlugin(IMultiprocessPlugin, Process):
32 | """
33 | Base class for multiprocessed plugin.
34 |
35 | DEPRECATED(>1.11): Please use IMultiProcessPluginBase instead !
36 | """
37 |
38 | def __init__(self, parent_pipe):
39 | IMultiprocessPlugin.__init__(self, parent_pipe)
40 | Process.__init__(self)
41 |
42 | def run(self):
43 | """
44 | Override this method in your implementation
45 | """
46 | return
47 |
--------------------------------------------------------------------------------
/package/yapsy/IMultiprocessPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | Defines the basic interfaces for multiprocessed plugins.
9 |
10 | Extensibility
11 | =============
12 |
13 | In your own software, you'll probably want to build derived classes of
14 | the ``IMultiprocessPlugin`` class as it is a mere interface with no specific
15 | functionality.
16 |
17 | Your software's plugins should then inherit your very own plugin class
18 | (itself derived from ``IMultiprocessPlugin``).
19 |
20 | Override the `run` method to include your code. Use the `self.parent_pipe` to send
21 | and receive data with the parent process or create your own communication
22 | mecanism.
23 |
24 | Where and how to code these plugins is explained in the section about
25 | the :doc:`PluginManager`.
26 |
27 | API
28 | ===
29 | """
30 |
31 | from yapsy.IPlugin import IPlugin
32 |
33 |
34 | class IMultiprocessPlugin(IPlugin):
35 | """
36 | Base class for multiprocessed plugin.
37 | """
38 |
39 | def __init__(self, parent_pipe):
40 | IPlugin.__init__(self)
41 | self.parent_pipe = parent_pipe
42 |
43 | def run(self):
44 | """
45 | Override this method in your implementation
46 | """
47 | return
48 |
--------------------------------------------------------------------------------
/package/yapsy/IPlugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | Defines the basic interfaces for a plugin. These interfaces are
9 | inherited by the *core* class of a plugin. The *core* class of a
10 | plugin is then the one that will be notified the
11 | activation/deactivation of a plugin via the ``activate/deactivate``
12 | methods.
13 |
14 |
15 | For simple (near trivial) plugin systems, one can directly use the
16 | following interfaces.
17 |
18 | Extensibility
19 | =============
20 |
21 | In your own software, you'll probably want to build derived classes of
22 | the ``IPlugin`` class as it is a mere interface with no specific
23 | functionality.
24 |
25 | Your software's plugins should then inherit your very own plugin class
26 | (itself derived from ``IPlugin``).
27 |
28 | Where and how to code these plugins is explained in the section about
29 | the :doc:`PluginManager`.
30 |
31 |
32 | API
33 | ===
34 | """
35 |
36 |
37 | class IPlugin(object):
38 | """
39 | The most simple interface to be inherited when creating a plugin.
40 | """
41 |
42 | def __init__(self):
43 | self.is_activated = False
44 |
45 | def activate(self):
46 | """
47 | Called at plugin activation.
48 | """
49 | self.is_activated = True
50 |
51 | def deactivate(self):
52 | """
53 | Called when the plugin is disabled.
54 | """
55 | self.is_activated = False
56 |
57 |
--------------------------------------------------------------------------------
/package/yapsy/IPluginLocator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 |
5 | """
6 | Role
7 | ====
8 |
9 | ``IPluginLocator`` defines the basic interface expected by a
10 | ``PluginManager`` to be able to locate plugins and get basic info
11 | about each discovered plugin (name, version etc).
12 |
13 | API
14 | ===
15 |
16 | """
17 |
18 |
19 | from yapsy import log
20 |
21 | class IPluginLocator(object):
22 | """
23 | Plugin Locator interface with some methods already implemented to
24 | manage the awkward backward compatible stuff.
25 | """
26 |
27 | def locatePlugins(self):
28 | """
29 | Walk through the plugins' places and look for plugins.
30 |
31 | Return the discovered plugins as a list of
32 | ``(candidate_infofile_path, candidate_file_path,plugin_info_instance)``
33 | and their number.
34 | """
35 | raise NotImplementedError("locatePlugins must be reimplemented by %s" % self)
36 |
37 | def gatherCorePluginInfo(self, directory, filename):
38 | """
39 | Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it.
40 |
41 | If filename is a valid plugin discovered by any of the known
42 | strategy in use. Returns None,None otherwise.
43 | """
44 | raise NotImplementedError("gatherPluginInfo must be reimplemented by %s" % self)
45 |
46 | # --------------------------------------------------------------------
47 | # Below are backward compatibility methods: if you inherit from
48 | # IPluginLocator it's ok not to reimplement them, there will only
49 | # be a warning message logged if they are called and not
50 | # reimplemented.
51 | # --------------------------------------------------------------------
52 |
53 | def getPluginNameAndModuleFromStream(self,fileobj):
54 | """
55 | DEPRECATED(>1.9): kept for backward compatibility
56 | with existing PluginManager child classes.
57 |
58 | Return a 3-uple with the name of the plugin, its
59 | module and the config_parser used to gather the core
60 | data *in a tuple*, if the required info could be
61 | localised, else return ``(None,None,None)``.
62 | """
63 | log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self)
64 | return None,None,None
65 |
66 |
67 | def setPluginInfoClass(self, picls, names=None):
68 | """
69 | DEPRECATED(>1.9): kept for backward compatibility
70 | with existing PluginManager child classes.
71 |
72 | Set the class that holds PluginInfo. The class should inherit
73 | from ``PluginInfo``.
74 | """
75 | log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self)
76 |
77 | def getPluginInfoClass(self):
78 | """
79 | DEPRECATED(>1.9): kept for backward compatibility
80 | with existing PluginManager child classes.
81 |
82 | Get the class that holds PluginInfo.
83 | """
84 | log.warning("getPluginInfoClass was called but '%s' doesn't implement it." % self)
85 | return None
86 |
87 | def setPluginPlaces(self, directories_list):
88 | """
89 | DEPRECATED(>1.9): kept for backward compatibility
90 | with existing PluginManager child classes.
91 |
92 | Set the list of directories where to look for plugin places.
93 | """
94 | log.warning("setPluginPlaces was called but '%s' doesn't implement it." % self)
95 |
96 | def updatePluginPlaces(self, directories_list):
97 | """
98 | DEPRECATED(>1.9): kept for backward compatibility
99 | with existing PluginManager child classes.
100 |
101 | Updates the list of directories where to look for plugin places.
102 | """
103 | log.warning("updatePluginPlaces was called but '%s' doesn't implement it." % self)
104 |
105 |
--------------------------------------------------------------------------------
/package/yapsy/MultiprocessPluginManager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | Role
5 | ====
6 |
7 | Defines a plugin manager that runs all plugins in separate process
8 | linked by pipes.
9 |
10 |
11 | API
12 | ===
13 | """
14 |
15 | import multiprocessing as mproc
16 |
17 | from yapsy.IMultiprocessPlugin import IMultiprocessPlugin
18 | from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin
19 | from yapsy.MultiprocessPluginProxy import MultiprocessPluginProxy
20 | from yapsy.PluginManager import PluginManager
21 |
22 |
23 | class MultiprocessPluginManager(PluginManager):
24 | """
25 | Subclass of the PluginManager that runs each plugin in a different process
26 | """
27 |
28 | def __init__(self,
29 | categories_filter=None,
30 | directories_list=None,
31 | plugin_info_ext=None,
32 | plugin_locator=None):
33 | if categories_filter is None:
34 | categories_filter = {"Default": IMultiprocessPlugin}
35 | PluginManager.__init__(self,
36 | categories_filter=categories_filter,
37 | directories_list=directories_list,
38 | plugin_info_ext=plugin_info_ext,
39 | plugin_locator=plugin_locator)
40 | self.connections = []
41 |
42 |
43 | def instanciateElementWithImportInfo(self, element, element_name,
44 | plugin_module_name, candidate_filepath):
45 | """This method instanciates each plugin in a new process and links it to
46 | the parent with a pipe.
47 |
48 | In the parent process context, the plugin's class is replaced by
49 | the ``MultiprocessPluginProxy`` class that hold the information
50 | about the child process and the pipe to communicate with it.
51 |
52 | .. warning::
53 | The plugin code should only use the pipe to
54 | communicate with the rest of the applica`tion and should not
55 | assume any kind of shared memory, not any specific functionality
56 | of the `multiprocessing.Process` parent class (its behaviour is
57 | different between platforms !)
58 |
59 | See ``IMultiprocessPlugin``.
60 | """
61 | if element is IMultiprocessChildPlugin:
62 | # The following will keep retro compatibility for IMultiprocessChildPlugin
63 | raise Exception("Preventing instanciation of a bar child plugin interface.")
64 | instanciated_element = MultiprocessPluginProxy()
65 | parent_pipe, child_pipe = mproc.Pipe()
66 | instanciated_element.child_pipe = parent_pipe
67 | instanciated_element.proc = MultiprocessPluginManager._PluginProcessWrapper(
68 | element_name, plugin_module_name, candidate_filepath,
69 | child_pipe)
70 | instanciated_element.proc.start()
71 | return instanciated_element
72 |
73 |
74 | class _PluginProcessWrapper(mproc.Process):
75 | """Helper class that strictly needed to be able to spawn the
76 | plugin on Windows but kept also for Unix platform to get a more
77 | uniform behaviour.
78 |
79 | This will handle re-importing the plugin's module in the child
80 | process (again this is necessary on windows because what has
81 | been imported in the main thread/process will not be shared with
82 | the spawned process.)
83 | """
84 | def __init__(self, element_name, plugin_module_name, candidate_filepath, child_pipe):
85 | self.element_name = element_name
86 | self.child_pipe = child_pipe
87 | self.plugin_module_name = plugin_module_name
88 | self.candidate_filepath = candidate_filepath
89 | mproc.Process.__init__(self)
90 |
91 | def run(self):
92 | module = PluginManager._importModule(self.plugin_module_name,
93 | self.candidate_filepath)
94 | element = getattr(module, self.element_name)
95 | e = element(self.child_pipe)
96 | e.run()
97 |
--------------------------------------------------------------------------------
/package/yapsy/MultiprocessPluginProxy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | The ``MultiprocessPluginProxy`` is instanciated by the MultiprocessPluginManager to replace the real implementation
9 | that is run in a different process.
10 |
11 | You cannot access your plugin directly from the parent process. You should use the child_pipe to communicate
12 | with your plugin. The `MultiprocessPluginProxy`` role is to keep reference of the communication pipe to the
13 | child process as well as the process informations.
14 |
15 | API
16 | ===
17 | """
18 |
19 | from yapsy.IPlugin import IPlugin
20 |
21 |
22 | class MultiprocessPluginProxy(IPlugin):
23 | """
24 | This class contains two members that are initialized by the :doc:`MultiprocessPluginManager`.
25 |
26 | self.proc is a reference that holds the multiprocessing.Process instance of the child process.
27 |
28 | self.child_pipe is a reference that holds the multiprocessing.Pipe instance to communicate with the child.
29 | """
30 | def __init__(self):
31 | IPlugin.__init__(self)
32 | self.proc = None # This attribute holds the multiprocessing.Process instance
33 | self.child_pipe = None # This attribute holds the multiprocessing.Pipe instance
34 |
--------------------------------------------------------------------------------
/package/yapsy/PluginInfo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | Encapsulate a plugin instance as well as some metadata.
9 |
10 | API
11 | ===
12 | """
13 |
14 |
15 | from configparser import ConfigParser
16 | from packaging.version import Version
17 |
18 |
19 | class PluginInfo(object):
20 | """Representation of the most basic set of information related to a
21 | given plugin such as its name, author, description...
22 |
23 | Any additional information can be stored ad retrieved in a
24 | PluginInfo, when this one is created with a
25 | ``ConfigParser.ConfigParser`` instance.
26 |
27 | This typically means that when metadata is read from a text file
28 | (the original way for yapsy to describe plugins), all info that is
29 | not part of the basic variables (name, path, version etc), can
30 | still be accessed though the ``details`` member variables that
31 | behaves like Python's ``ConfigParser.ConfigParser``.
32 |
33 | .. warning::
34 | The instance associated with the ``details`` member
35 | variable is never copied and used to store all plugin infos. If
36 | you set it to a custom instance, it will be modified as soon as
37 | another member variale of the plugin info is
38 | changed. Alternatively, if you change the instance "outside" the
39 | plugin info, it will also change the plugin info.
40 |
41 | Ctor Arguments:
42 |
43 | :plugin_name: is a simple string describing the name of
44 | the plugin.
45 |
46 | :plugin_path: describe the location where the plugin can be
47 | found.
48 |
49 | .. warning::
50 | The ``path`` attribute is the full path to the
51 | plugin if it is organised as a directory or the
52 | full path to a file without the ``.py`` extension
53 | if the plugin is defined by a simple file. In the
54 | later case, the actual plugin is reached via
55 | ``plugin_info.path+'.py'``.
56 | """
57 |
58 | def __init__(self, plugin_name, plugin_path):
59 | self.__details = ConfigParser()
60 | self.name = plugin_name
61 | self.path = plugin_path
62 | self._ensureDetailsDefaultsAreBackwardCompatible()
63 | # Storage for stuff created during the plugin lifetime
64 | self.plugin_object = None
65 | self.categories = []
66 | self.error = None
67 |
68 |
69 | def __setDetails(self,cfDetails):
70 | """
71 | Fill in all details by storing a ``ConfigParser`` instance.
72 |
73 | .. warning::
74 | The values for ``plugin_name`` and
75 | ``plugin_path`` given a init time will superseed
76 | any value found in ``cfDetails`` in section
77 | 'Core' for the options 'Name' and 'Module' (this
78 | is mostly for backward compatibility).
79 | """
80 | bkp_name = self.name
81 | bkp_path = self.path
82 | self.__details = cfDetails
83 | self.name = bkp_name
84 | self.path = bkp_path
85 | self._ensureDetailsDefaultsAreBackwardCompatible()
86 |
87 | def __getDetails(self):
88 | return self.__details
89 |
90 | def __getName(self):
91 | return self.details.get("Core","Name")
92 |
93 | def __setName(self, name):
94 | if not self.details.has_section("Core"):
95 | self.details.add_section("Core")
96 | self.details.set("Core","Name",name)
97 |
98 |
99 | def __getPath(self):
100 | return self.details.get("Core","Module")
101 |
102 | def __setPath(self,path):
103 | if not self.details.has_section("Core"):
104 | self.details.add_section("Core")
105 | self.details.set("Core","Module",path)
106 |
107 |
108 | def __getVersion(self):
109 | return Version(self.details.get("Documentation","Version"))
110 |
111 | def setVersion(self, vstring):
112 | """
113 | Set the version of the plugin.
114 |
115 | Used by subclasses to provide different handling of the
116 | version number.
117 | """
118 | if isinstance(vstring,Version):
119 | vstring = str(vstring)
120 | if not self.details.has_section("Documentation"):
121 | self.details.add_section("Documentation")
122 | self.details.set("Documentation","Version",vstring)
123 |
124 | def __getAuthor(self):
125 | return self.details.get("Documentation","Author")
126 |
127 | def __setAuthor(self,author):
128 | if not self.details.has_section("Documentation"):
129 | self.details.add_section("Documentation")
130 | self.details.set("Documentation","Author",author)
131 |
132 |
133 | def __getCopyright(self):
134 | return self.details.get("Documentation","Copyright")
135 |
136 | def __setCopyright(self,copyrightTxt):
137 | if not self.details.has_section("Documentation"):
138 | self.details.add_section("Documentation")
139 | self.details.set("Documentation","Copyright",copyrightTxt)
140 |
141 |
142 | def __getWebsite(self):
143 | return self.details.get("Documentation","Website")
144 |
145 | def __setWebsite(self,website):
146 | if not self.details.has_section("Documentation"):
147 | self.details.add_section("Documentation")
148 | self.details.set("Documentation","Website",website)
149 |
150 |
151 | def __getDescription(self):
152 | return self.details.get("Documentation","Description")
153 |
154 | def __setDescription(self,description):
155 | if not self.details.has_section("Documentation"):
156 | self.details.add_section("Documentation")
157 | return self.details.set("Documentation","Description",description)
158 |
159 |
160 | def __getCategory(self):
161 | """
162 | DEPRECATED (>1.9): Mimic former behaviour when what is
163 | noz the first category was considered as the only one the
164 | plugin belonged to.
165 | """
166 | if self.categories:
167 | return self.categories[0]
168 | else:
169 | return "UnknownCategory"
170 |
171 | def __setCategory(self,c):
172 | """
173 | DEPRECATED (>1.9): Mimic former behaviour by making so
174 | that if a category is set as if it were the only category to
175 | which the plugin belongs, then a __getCategory will return
176 | this newly set category.
177 | """
178 | self.categories = [c] + self.categories
179 |
180 | name = property(fget=__getName,fset=__setName)
181 | path = property(fget=__getPath,fset=__setPath)
182 | version = property(fget=__getVersion,fset=setVersion)
183 | author = property(fget=__getAuthor,fset=__setAuthor)
184 | copyright = property(fget=__getCopyright,fset=__setCopyright)
185 | website = property(fget=__getWebsite,fset=__setWebsite)
186 | description = property(fget=__getDescription,fset=__setDescription)
187 | details = property(fget=__getDetails,fset=__setDetails)
188 | # deprecated (>1.9): plugins are not longer associated to a
189 | # single category !
190 | category = property(fget=__getCategory,fset=__setCategory)
191 |
192 | def _getIsActivated(self):
193 | """
194 | Return the activated state of the plugin object.
195 | Makes it possible to define a property.
196 | """
197 | return self.plugin_object.is_activated
198 |
199 | is_activated = property(fget=_getIsActivated)
200 |
201 | def _ensureDetailsDefaultsAreBackwardCompatible(self):
202 | """
203 | Internal helper function.
204 | """
205 | if not self.details.has_option("Documentation","Author"):
206 | self.author = "Unknown"
207 | if not self.details.has_option("Documentation","Version"):
208 | self.version = "0.0"
209 | if not self.details.has_option("Documentation","Website"):
210 | self.website = "None"
211 | if not self.details.has_option("Documentation","Copyright"):
212 | self.copyright = "Unknown"
213 | if not self.details.has_option("Documentation","Description"):
214 | self.description = ""
215 |
--------------------------------------------------------------------------------
/package/yapsy/PluginManagerDecorator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 |
4 | """
5 | Role
6 | ====
7 |
8 | Provide an easy way to build a chain of decorators extending the
9 | functionalities of the default plugin manager, when it comes to
10 | activating, deactivating or looking into loaded plugins.
11 |
12 | The ``PluginManagerDecorator`` is the base class to be inherited by
13 | each element of the chain of decorator.
14 |
15 | .. warning:: If you want to customise the way the plugins are detected
16 | and loaded, you should not try to do it by implementing a
17 | new ``PluginManagerDecorator``. Instead, you'll have to
18 | reimplement the :doc:`PluginManager` itself. And if you
19 | do so by enforcing the ``PluginManager`` interface, just
20 | giving an instance of your new manager class to the
21 | ``PluginManagerDecorator`` should be transparent to the
22 | "stantard" decorators.
23 |
24 | API
25 | ===
26 | """
27 |
28 | import os
29 |
30 | from yapsy.IPlugin import IPlugin
31 | from yapsy import log
32 |
33 |
34 | class PluginManagerDecorator(object):
35 | """
36 | Add several responsibilities to a plugin manager object in a
37 | more flexible way than by mere subclassing. This is indeed an
38 | implementation of the Decorator Design Patterns.
39 |
40 |
41 | There is also an additional mechanism that allows for the
42 | automatic creation of the object to be decorated when this object
43 | is an instance of PluginManager (and not an instance of its
44 | subclasses). This way we can keep the plugin managers creation
45 | simple when the user don't want to mix a lot of 'enhancements' on
46 | the base class.
47 |
48 |
49 | About the __init__:
50 |
51 | Mimics the PluginManager's __init__ method and wraps an
52 | instance of this class into this decorator class.
53 |
54 | - *If the decorated_object is not specified*, then we use the
55 | PluginManager class to create the 'base' manager, and to do
56 | so we will use the arguments: ``categories_filter``,
57 | ``directories_list``, and ``plugin_info_ext`` or their
58 | default value if they are not given.
59 | - *If the decorated object is given*, these last arguments are
60 | simply **ignored** !
61 |
62 | All classes (and especially subclasses of this one) that want
63 | to be a decorator must accept the decorated manager as an
64 | object passed to the init function under the exact keyword
65 | ``decorated_object``.
66 | """
67 |
68 | def __init__(self, decorated_object=None,
69 | # The following args will only be used if we need to
70 | # create a default PluginManager
71 | categories_filter=None,
72 | directories_list=None,
73 | plugin_info_ext="yapsy-plugin"):
74 | if directories_list is None:
75 | directories_list = [os.path.dirname(__file__)]
76 | if categories_filter is None:
77 | categories_filter = {"Default": IPlugin}
78 | if decorated_object is None:
79 | log.debug("Creating a default PluginManager instance to be decorated.")
80 | from yapsy.PluginManager import PluginManager
81 | decorated_object = PluginManager(categories_filter,
82 | directories_list,
83 | plugin_info_ext)
84 | self._component = decorated_object
85 |
86 | def __getattr__(self,name):
87 | """
88 | Decorator trick copied from:
89 | http://www.pasteur.fr/formation/infobio/python/ch18s06.html
90 | """
91 | # print "looking for %s in %s" % (name, self.__class__)
92 | return getattr(self._component,name)
93 |
94 |
95 | def collectPlugins(self):
96 | """
97 | This function will usually be a shortcut to successively call
98 | ``self.locatePlugins`` and then ``self.loadPlugins`` which are
99 | very likely to be redefined in each new decorator.
100 |
101 | So in order for this to keep on being a "shortcut" and not a
102 | real pain, I'm redefining it here.
103 | """
104 | self.locatePlugins()
105 | self.loadPlugins()
106 |
--------------------------------------------------------------------------------
/package/yapsy/VersionedPluginManager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 | Role
5 | ====
6 |
7 | Defines the basic interface for a plugin manager that also keeps track
8 | of versions of plugins
9 |
10 | API
11 | ===
12 | """
13 |
14 |
15 | from packaging.version import Version
16 |
17 | from yapsy.PluginInfo import PluginInfo
18 | from yapsy.IPlugin import IPlugin
19 | from yapsy.PluginManagerDecorator import PluginManagerDecorator
20 |
21 |
22 | class VersionedPluginInfo(PluginInfo):
23 | """
24 | Gather some info about a plugin such as its name, author,
25 | description...
26 | """
27 |
28 | def __init__(self, plugin_name, plugin_path):
29 | PluginInfo.__init__(self, plugin_name, plugin_path)
30 | # version number is now required to be a Version object
31 | self.version = Version("0.0")
32 |
33 | def setVersion(self, vstring):
34 | self.version = Version(vstring)
35 |
36 |
37 | class VersionedPluginManager(PluginManagerDecorator):
38 | """
39 | Handle plugin versioning by making sure that when several
40 | versions are present for a same plugin, only the latest version is
41 | manipulated via the standard methods (eg for activation and
42 | deactivation)
43 |
44 | More precisely, for operations that must be applied on a single
45 | named plugin at a time (``getPluginByName``,
46 | ``activatePluginByName``, ``deactivatePluginByName`` etc) the
47 | targetted plugin will always be the one with the latest version.
48 |
49 | .. note:: The older versions of a given plugin are still reachable
50 | via the ``getPluginsOfCategoryFromAttic`` method.
51 | """
52 |
53 | def __init__(self,
54 | decorated_manager=None,
55 | categories_filter={"Default":IPlugin},
56 | directories_list=None,
57 | plugin_info_ext="yapsy-plugin"):
58 | # Create the base decorator class
59 | PluginManagerDecorator.__init__(self,decorated_manager,
60 | categories_filter,
61 | directories_list,
62 | plugin_info_ext)
63 | self.setPluginInfoClass(VersionedPluginInfo)
64 | # prepare the storage for the early version of the plugins,
65 | # for which only the latest version is the one that will be
66 | # kept in the "core" plugin storage.
67 | self._prepareAttic()
68 |
69 | def _prepareAttic(self):
70 | """
71 | Create and correctly initialize the storage where the wrong
72 | version of the plugins will be stored.
73 | """
74 | self._attic = {}
75 | for categ in self.getCategories():
76 | self._attic[categ] = []
77 |
78 | def setCategoriesFilter(self, categories_filter):
79 | """
80 | Set the categories of plugins to be looked for as well as the
81 | way to recognise them.
82 |
83 | Note: will also reset the attic toa void inconsistencies.
84 | """
85 | self._component.setCategoriesFilter(categories_filter)
86 | self._prepareAttic()
87 |
88 | def getLatestPluginsOfCategory(self,category_name):
89 | """
90 | DEPRECATED(>1.8): Please consider using getPluginsOfCategory
91 | instead.
92 |
93 | Return the list of all plugins belonging to a category.
94 | """
95 | return self.getPluginsOfCategory(category_name)
96 |
97 | def loadPlugins(self, callback=None, callback_after=None):
98 | """
99 | Load the candidate plugins that have been identified through a
100 | previous call to locatePlugins.
101 |
102 | In addition to the baseclass functionality, this subclass also
103 | needs to find the latest version of each plugin.
104 | """
105 | self._prepareAttic()
106 | self._component.loadPlugins(callback, callback_after)
107 | for categ in self.getCategories():
108 | latest_plugins = {}
109 | allPlugins = self.getPluginsOfCategory(categ)
110 | # identify the latest version of each plugin
111 | for plugin in allPlugins:
112 | name = plugin.name
113 | version = plugin.version
114 | if name in latest_plugins:
115 | if version > latest_plugins[name].version:
116 | older_plugin = latest_plugins[name]
117 | latest_plugins[name] = plugin
118 | self.removePluginFromCategory(older_plugin,categ)
119 | self._attic[categ].append(older_plugin)
120 | else:
121 | self.removePluginFromCategory(plugin,categ)
122 | self._attic[categ].append(plugin)
123 | else:
124 | latest_plugins[name] = plugin
125 |
126 | def getPluginsOfCategoryFromAttic(self,categ):
127 | """
128 | Access the older version of plugins for which only the latest
129 | version is available through standard methods.
130 | """
131 | return self._attic[categ]
132 |
133 |
--------------------------------------------------------------------------------
/package/yapsy/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*-
2 |
3 | """
4 |
5 | Overview
6 | ========
7 |
8 | Yapsy's main purpose is to offer a way to easily design a plugin
9 | system in Python, and motivated by the fact that many other Python
10 | plugin system are either too complicated for a basic use or depend on
11 | a lot of libraries. Yapsy only depends on Python's standard library.
12 |
13 | |yapsy| basically defines two core classes:
14 |
15 | - a fully functional though very simple ``PluginManager`` class
16 |
17 | - an interface ``IPlugin`` which defines the interface of plugin
18 | instances handled by the ``PluginManager``
19 |
20 |
21 | Getting started
22 | ===============
23 |
24 | The basic classes defined by |yapsy| should work "as is" and enable
25 | you to load and activate your plugins. So that the following code
26 | should get you a fully working plugin management system::
27 |
28 | from yapsy.PluginManager import PluginManager
29 |
30 | # Build the manager
31 | simplePluginManager = PluginManager()
32 | # Tell it the default place(s) where to find plugins
33 | simplePluginManager.setPluginPlaces(["path/to/myplugins"])
34 | # Load all plugins
35 | simplePluginManager.collectPlugins()
36 |
37 | # Activate all loaded plugins
38 | for pluginInfo in simplePluginManager.getAllPlugins():
39 | simplePluginManager.activatePluginByName(pluginInfo.name)
40 |
41 |
42 | .. note:: The ``plugin_info`` object (typically an instance of
43 | ``IPlugin``) plays as *the entry point of each
44 | plugin*. That's also where |yapsy| ceases to guide you: it's
45 | up to you to define what your plugins can do and how you
46 | want to talk to them ! Talking to your plugin will then look
47 | very much like the following::
48 |
49 | # Trigger 'some action' from the loaded plugins
50 | for pluginInfo in simplePluginManager.getAllPlugins():
51 | pluginInfo.plugin_object.doSomething(...)
52 |
53 | """
54 |
55 | __version__="2.0.0"
56 |
57 | # tell epydoc that the documentation is in the reStructuredText format
58 | __docformat__ = "restructuredtext en"
59 |
60 | # provide a default named log for package-wide use
61 | import logging
62 | log = logging.getLogger('yapsy')
63 |
64 | # Some constants concerning the plugins
65 | PLUGIN_NAME_FORBIDEN_STRING=";;"
66 | """
67 | .. warning:: This string (';;' by default) is forbidden in plugin
68 | names, and will be usable to describe lists of plugins
69 | for instance (see :doc:`ConfigurablePluginManager`)
70 | """
71 |
72 | import re
73 | RE_NON_ALPHANUM = re.compile(r"\W")
74 |
75 |
76 | def NormalizePluginNameForModuleName(pluginName):
77 | """
78 | Normalize a plugin name into a safer name for a module name.
79 |
80 | .. note:: may do a little more modifications than strictly
81 | necessary and is not optimized for speed.
82 | """
83 | if len(pluginName)==0:
84 | return "_"
85 | if pluginName[0].isdigit():
86 | pluginName = "_" + pluginName
87 | ret = RE_NON_ALPHANUM.sub("_",pluginName)
88 | return ret
89 |
--------------------------------------------------------------------------------
/requirements-release.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | setuptools
3 | twine
4 |
5 |
--------------------------------------------------------------------------------
/requirements-tests.txt:
--------------------------------------------------------------------------------
1 |
2 | coverage
3 | pytest
4 | flake8
5 | pytest-cov
6 |
--------------------------------------------------------------------------------
/utils/gen_doc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -*- coding: utf-8 -*-
3 |
4 | # Enter release environment
5 | RELEASE_ENV=$( pwd )/$( dirname $0 )/release_env.sh
6 | source $RELEASE_ENV
7 | activate_release_env
8 |
9 | rm -r $PACKAGE_DIR/build
10 |
11 | # Generate doc
12 | python $PACKAGE_DIR/setup.py build_sphinx
13 |
14 | # Exit release environment
15 | deactivate_release_env
16 |
--------------------------------------------------------------------------------
/utils/release_env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -*- coding: utf-8 -*-
3 |
4 | # Designed to be source'd by other scripts.
5 | # Not to be called directly !
6 | #
7 | # This defines a few useful variables and allows to create a valid
8 | # environment for all "release" related actions of the project.
9 |
10 |
11 | PROJECT_DIR="$( dirname $0 )"/..
12 | PACKAGE_DIR=$PROJECT_DIR/package
13 | RELEASE_ENV=$PROJECT_DIR/release_env
14 | RELEASE_ENV_REQS=$PROJECT_DIR/requirements-release.txt
15 |
16 |
17 | activate_release_env() {
18 | if [ ! -d $RELEASE_ENV ]; then
19 | echo "Creating a virtual env." && python -m venv $RELEASE_ENV
20 | fi
21 | source $RELEASE_ENV/bin/activate
22 | pip install -U pip
23 | pip install -r $RELEASE_ENV_REQS
24 | }
25 |
26 | deactivate_release_env() {
27 | deactivate
28 | }
29 |
--------------------------------------------------------------------------------
/utils/upload_packages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -*- coding: utf-8 -*-
3 |
4 | # Enter release environment
5 | RELEASE_ENV=$( pwd )/$( dirname $0 )/release_env.sh
6 | source $RELEASE_ENV
7 | activate_release_env
8 |
9 |
10 | # Generate package
11 | rm -r $PACKAGE_DIR/build
12 | rm -r $PACKAGE_DIR/dist
13 | python3 $PACKAGE_DIR/setup.py bdist_egg
14 |
15 | # Upload the package
16 | twine upload $PACKAGE_DIR/dist/*
17 |
18 | # Exit relase environment
19 | deactivate_release_env
20 |
21 |
--------------------------------------------------------------------------------