summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2014-07-01 10:12:10 +0200
committerArmin Ronacher <armin.ronacher@active-4.com>2014-07-01 10:12:10 +0200
commitc398d967e0281540d84e4ba2c14424eb1944288a (patch)
treec0638db9dda2a0998854d470c88511dad905559d
parent08f0c0b112e6cdaa7dbf2e830c7fe95f513acd6d (diff)
downloadpluginbase-c398d967e0281540d84e4ba2c14424eb1944288a.tar.gz
Added support for loading resources.
-rw-r--r--pluginbase.py31
-rw-r--r--tests/plugins/withresources/__init__.py2
-rw-r--r--tests/plugins/withresources/hello.txt1
-rw-r--r--tests/test_advanced.py12
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')