diff options
author | Armin Ronacher <armin.ronacher@active-4.com> | 2014-07-01 10:12:10 +0200 |
---|---|---|
committer | Armin Ronacher <armin.ronacher@active-4.com> | 2014-07-01 10:12:10 +0200 |
commit | c398d967e0281540d84e4ba2c14424eb1944288a (patch) | |
tree | c0638db9dda2a0998854d470c88511dad905559d | |
parent | 08f0c0b112e6cdaa7dbf2e830c7fe95f513acd6d (diff) | |
download | pluginbase-c398d967e0281540d84e4ba2c14424eb1944288a.tar.gz |
Added support for loading resources.
-rw-r--r-- | pluginbase.py | 31 | ||||
-rw-r--r-- | tests/plugins/withresources/__init__.py | 2 | ||||
-rw-r--r-- | tests/plugins/withresources/hello.txt | 1 | ||||
-rw-r--r-- | tests/test_advanced.py | 12 |
4 files changed, 46 insertions, 0 deletions
diff --git a/pluginbase.py b/pluginbase.py index 426a105..1158ed9 100644 --- a/pluginbase.py +++ b/pluginbase.py @@ -9,8 +9,10 @@ :copyright: (c) Copyright 2014 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import os import sys import uuid +import errno import pkgutil import hashlib import threading @@ -23,9 +25,11 @@ PY2 = sys.version_info[0] == 2 if PY2: text_type = unicode string_types = (unicode, str) + from cStringIO import StringIO as NativeBytesIO else: text_type = str string_types = (str,) + from io import BytesIO as NativeBytesIO _local = threading.local() @@ -261,10 +265,36 @@ class PluginSource(object): :param name: the name of the plugin to load. """ + if '.' in name: + raise ImportError('Plugin names cannot contain dots.') with self: return __import__(self.base.package + '.' + name, globals(), {}, ['__name__']) + def open_resource(self, plugin, filename): + """This function locates a resource inside the plugin and returns + a byte stream to the contents of it. If the resource cannot be + loaded an :exc:`IOError` will be raised. Only plugins that are + real Python packages can contain resources. Plain old Python + modules do not allow this for obvious reasons. + + .. versionadded:: 0.3 + + :param plugin: the name of the plugin to open the resource of. + :param filename: the name of the file within the plugin to open. + """ + mod = self.load_plugin(plugin) + fn = getattr(mod, '__file__', None) + if fn is not None: + if fn.endswith(('.pyc', '.pyo')): + fn = fn[:-1] + if os.path.isfile(fn): + return open(os.path.join(os.path.dirname(fn), filename), 'rb') + buf = pkgutil.get_data(self.mod.__name__ + '.' + plugin, filename) + if buf is None: + raise IOError(errno.ENOEXITS, 'Could not find resource') + return NativeBytesIO(buf) + def cleanup(self): """Cleans up all loaded plugins manually. This is necessary to call only if :attr:`persist` is enabled. Otherwise this happens @@ -369,6 +399,7 @@ class _ImportHook(ModuleType): actual_name = space._rewrite_module_path(name) if actual_name is not None: import_name = actual_name + return self._system_import(import_name, globals, locals, fromlist, level) diff --git a/tests/plugins/withresources/__init__.py b/tests/plugins/withresources/__init__.py new file mode 100644 index 0000000..9332a27 --- /dev/null +++ b/tests/plugins/withresources/__init__.py @@ -0,0 +1,2 @@ +def foo(): + pass diff --git a/tests/plugins/withresources/hello.txt b/tests/plugins/withresources/hello.txt new file mode 100644 index 0000000..a670a4e --- /dev/null +++ b/tests/plugins/withresources/hello.txt @@ -0,0 +1 @@ +I am a textfile. diff --git a/tests/test_advanced.py b/tests/test_advanced.py index d6440a5..15f668f 100644 --- a/tests/test_advanced.py +++ b/tests/test_advanced.py @@ -1,3 +1,6 @@ +import pytest + + def test_custom_state(base): class App(object): name = 'foobar' @@ -6,3 +9,12 @@ def test_custom_state(base): plg = source.load_plugin('advanced') assert plg.get_app_name() == 'foobar' + + +def test_plugin_resources(source): + with source.open_resource('withresources', 'hello.txt') as f: + contents = f.read() + assert contents == b'I am a textfile.\n' + + with pytest.raises(IOError): + source.open_resource('withresources', 'missingfile.txt') |