summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt5
-rw-r--r--TODO.txt2
-rw-r--r--docs/api.rst1
-rw-r--r--docs/api/path.rst13
-rw-r--r--docs/glossary.rst6
-rw-r--r--docs/whatsnew-1.3.rst6
-rw-r--r--pyramid/asset.py1
-rw-r--r--pyramid/config/__init__.py3
-rw-r--r--pyramid/interfaces.py38
-rw-r--r--pyramid/path.py301
-rw-r--r--pyramid/tests/test_asset.py25
-rw-r--r--pyramid/tests/test_path.py350
-rw-r--r--pyramid/tests/test_util.py183
-rw-r--r--pyramid/util.py144
14 files changed, 727 insertions, 351 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 67cac4b2b..4be846ece 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -38,6 +38,11 @@ Features
- Allow extra keyword arguments to be passed to the
``pyramid.config.Configurator.action`` method.
+- New APIs: ``pyramid.path.AssetResolver`` and
+ ``pyramid.path.DottedNameResolver``. The former can be used to resolve
+ asset specifications, the latter can be used to resolve dotted names to
+ modules or packages.
+
Bug Fixes
---------
diff --git a/TODO.txt b/TODO.txt
index a398f7b49..9404ebb6c 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -34,6 +34,8 @@ Must-Have
- Implement analogue of "paster request"?
+- AssetResolver method to guess relative based on caller?
+
Nice-to-Have
------------
diff --git a/docs/api.rst b/docs/api.rst
index dc75e45e0..979e8f490 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -21,6 +21,7 @@ documentation is organized alphabetically by module name.
api/interfaces
api/location
api/paster
+ api/path
api/registry
api/renderers
api/request
diff --git a/docs/api/path.rst b/docs/api/path.rst
new file mode 100644
index 000000000..045d77da2
--- /dev/null
+++ b/docs/api/path.rst
@@ -0,0 +1,13 @@
+.. _path_module:
+
+:mod:`pyramid.path`
+---------------------------
+
+.. automodule:: pyramid.path
+
+ .. autoclass:: DottedNameResolver
+ :members:
+
+ .. autoclass:: AssetResolver
+ :members:
+
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 399b78cdf..e4de15bd6 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -977,3 +977,9 @@ Glossary
a running Pyramid application. An introspectable is associated with a
:term:`action` by virtue of the
:meth:`pyramid.config.Configurator.action` method.
+
+ asset descriptor
+ An instance representing an :term:`asset specification` provided by the
+ :meth:`pyramid.path.AssetResolver.resolve` method. It supports the
+ methods and attributes documented in
+ :class:`pyramid.interfaces.IAssetDescriptor`.
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index 44c55e208..9bb10d1d0 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -43,6 +43,12 @@ New APIs were added to support introspection
Minor Feature Additions
-----------------------
+- New APIs: :class:`pyramid.path.AssetResolver` and
+ :class:`pyramid.path.DottedNameResolver`. The former can be used to
+ resolve an :term:`asset specification` to an API that can be used to read
+ the asset's data, the latter can be used to resolve a :term:`dotted Python
+ name` to a module or a package.
+
- A ``mako.directories`` setting is no longer required to use Mako templates
Rationale: Mako template renderers can be specified using an absolute asset
spec. An entire application can be written with such asset specs,
diff --git a/pyramid/asset.py b/pyramid/asset.py
index 63be78c72..1cb65fbb1 100644
--- a/pyramid/asset.py
+++ b/pyramid/asset.py
@@ -40,3 +40,4 @@ def abspath_from_asset_spec(spec, pname='__main__'):
if pname is None:
return filename
return pkg_resources.resource_filename(pname, filename)
+
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 315cdef07..11f1758fc 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -53,7 +53,6 @@ from pyramid.settings import aslist
from pyramid.threadlocal import manager
from pyramid.util import (
- DottedNameResolver,
WeakOrderedSet,
object_description,
)
@@ -76,6 +75,8 @@ from pyramid.config.util import (
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
+from pyramid.path import DottedNameResolver
+
empty = text_('')
_marker = object()
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 0261ae3db..6762d788d 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -1013,6 +1013,44 @@ class IActionInfo(Interface):
""" Return a representation of the action information (including
source code from file, if possible) """
+class IAssetDescriptor(Interface):
+ """
+ Describes an :term:`asset`.
+ """
+
+ def absspec():
+ """
+ Returns the absolute asset specification for this asset
+ (e.g. ``mypackage:templates/foo.pt``).
+ """
+
+ def abspath():
+ """
+ Returns an absolute path in the filesystem to the asset.
+ """
+
+ def stream():
+ """
+ Returns an input stream for reading asset contents. Raises an
+ exception if the asset is a directory or does not exist.
+ """
+
+ def isdir():
+ """
+ Returns True if the asset is a directory, otherwise returns False.
+ """
+
+ def listdir():
+ """
+ Returns iterable of filenames of directory contents. Raises an
+ exception if asset is not a directory.
+ """
+
+ def exists():
+ """
+ Returns True if asset exists, otherwise returns False.
+ """
+
# configuration phases: a lower phase number means the actions associated
# with this phase will be executed earlier than those with later phase
# numbers. The default phase number is 0, FTR.
diff --git a/pyramid/path.py b/pyramid/path.py
index 86c1c5857..05a54fff7 100644
--- a/pyramid/path.py
+++ b/pyramid/path.py
@@ -3,6 +3,12 @@ import pkg_resources
import sys
import imp
+from zope.interface import implementer
+
+from pyramid.interfaces import IAssetDescriptor
+
+from pyramid.compat import string_types
+
ignore_types = [ imp.C_EXTENSION, imp.C_BUILTIN ]
init_names = [ '__init__%s' % x[0] for x in imp.get_suffixes() if
x[0] and x[2] not in ignore_types ]
@@ -68,3 +74,298 @@ def package_path(package):
pass
return prefix
+class Resolver(object):
+ def __init__(self, package=None):
+ if package is None:
+ self.package_name = None
+ self.package = None
+ else:
+ if isinstance(package, string_types):
+ try:
+ __import__(package)
+ except ImportError:
+ raise ValueError(
+ 'The dotted name %r cannot be imported' % (package,)
+ )
+ package = sys.modules[package]
+ self.package = package_of(package)
+ self.package_name = self.package.__name__
+
+class AssetResolver(Resolver):
+ """ A class used to resolve an :term:`asset specification` to an
+ :term:`asset descriptor`.
+
+ .. warning:: This API is new as of Pyramid 1.3.
+
+ The constructor accepts a single argument named ``package`` which may be
+ any of:
+
+ - A fully qualified (not relative) dotted name to a module or package
+
+ - a Python module or package object
+
+ - The value ``None``
+
+ The ``package`` is used when a relative asset specification is supplied
+ to the :meth:`~pyramid.path.AssetResolver.resolve` method. An asset
+ specification without a colon in it is treated as relative.
+
+ If the value ``None`` is supplied as the package name, the resolver will
+ only be able to resolve fully qualified (not relative) asset
+ specifications. Any attempt to resolve a relative asset specification
+ when the ``package`` is ``None`` will result in an :exc:`ValueError`
+ exception.
+
+ If a *module* or *module name* (as opposed to a package or package name)
+ is supplied as ``package``, its containing package is computed and this
+ package used to derive the package name (all names are resolved relative
+ to packages, never to modules). For example, if the ``package`` argument
+ to this type was passed the string ``xml.dom.expatbuilder``, and
+ ``template.pt`` is supplied to the
+ :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute
+ asset spec would be ``xml.minidom:template.pt``, because
+ ``xml.dom.expatbuilder`` is a module object, not a package object.
+
+ If a *package* or *package name* (as opposed to a module or module name)
+ is supplied as ``package``, this package will be used to compute relative
+ asset specifications. For example, if the ``package`` argument to this
+ type was passed the string ``xml.dom``, and ``template.pt`` is supplied
+ to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting
+ absolute asset spec would be ``xml.minidom:template.pt``.
+ """
+ def resolve(self, spec):
+ """
+ Resolve the asset spec named as ``spec`` to an object that has the
+ attributes and methods described in
+ `pyramid.interfaces.IAssetDescriptor`.
+
+ If ``spec`` is an absolute filename
+ (e.g. ``/path/to/myproject/templates/foo.pt``) or an absolute asset
+ spec (e.g. ``myproject:templates.foo.pt``), an asset descriptor is
+ returned without taking into account the ``package`` passed to this
+ class' constructor.
+
+ If ``spec`` is a *relative* asset specification (an asset
+ specification without a ``:`` in it, e.g. ``templates/foo.pt``), the
+ ``package`` argument of the constructor is used as the the package
+ portion of the asset spec. For example:
+
+ .. code-block:: python
+
+ a = AssetResolver('myproject')
+ resolver = a.resolve('templates/foo.pt')
+ print resolver.abspath()
+ # -> /path/to/myproject/templates/foo.pt
+
+ If the AssetResolver is constructed without a ``package`` argument,
+ and a relative asset specification is passed to ``resolve``, a
+ :exc:`ValueError` exception is raised.
+ """
+ if os.path.isabs(spec):
+ return FSAssetDescriptor(spec)
+ path = spec
+ if ':' in path:
+ pkg_name, path = spec.split(':', 1)
+ else:
+ pkg_name = self.package_name
+ if pkg_name is None:
+ raise ValueError(
+ 'relative spec %r irresolveable without package' % (spec,)
+ )
+ return PkgResourcesAssetDescriptor(pkg_name, path)
+
+class DottedNameResolver(Resolver):
+ """ A class used to resolve a :term:`dotted Python name` to a package or
+ module object.
+
+ .. warning:: This API is new as of Pyramid 1.3.
+
+ The constructor accepts a single argument named ``package`` which may be
+ any of:
+
+ - A fully qualified (not relative) dotted name to a module or package
+
+ - a Python module or package object
+
+ - The value ``None``
+
+ The ``package`` is used when a relative dotted name is supplied to the
+ :meth:`~pyramid.path.DottedNameResolver.resolve` method. A dotted name
+ which has a ``.`` (dot) or ``:`` (colon) as its first character is
+ treated as relative.
+
+ If the value ``None`` is supplied as the package name, the resolver will
+ only be able to resolve fully qualified (not relative) names. Any
+ attempt to resolve a relative name when the ``package`` is ``None`` will
+ result in an :exc:`ValueError` exception.
+
+ If a *module* or *module name* (as opposed to a package or package name)
+ is supplied as ``package``, its containing package is computed and this
+ package used to derive the package name (all names are resolved relative
+ to packages, never to modules). For example, if the ``package`` argument
+ to this type was passed the string ``xml.dom.expatbuilder``, and
+ ``.mindom`` is supplied to the
+ :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting
+ import would be for ``xml.minidom``, because ``xml.dom.expatbuilder`` is
+ a module object, not a package object.
+
+ If a *package* or *package name* (as opposed to a module or module name)
+ is supplied as ``package``, this package will be used to relative compute
+ dotted names. For example, if the ``package`` argument to this type was
+ passed the string ``xml.dom``, and ``.minidom`` is supplied to the
+ :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting
+ import would be for ``xml.minidom``.
+
+ When a dotted name cannot be resolved, a :exc:`ValueError` error is
+ raised.
+ """
+ def resolve(self, name):
+ """
+ This method resolves a dotted name reference to a global Python
+ object (an object which can be imported) to the object itself.
+
+ Two dotted name styles are supported:
+
+ - ``pkg_resources``-style dotted names where non-module attributes
+ of a package are separated from the rest of the path using a ``:``
+ e.g. ``package.module:attr``.
+
+ - ``zope.dottedname``-style dotted names where non-module
+ attributes of a package are separated from the rest of the path
+ using a ``.`` e.g. ``package.module.attr``.
+
+ These styles can be used interchangeably. If the supplied name
+ contains a ``:`` (colon), the ``pkg_resources`` resolution
+ mechanism will be chosen, otherwise the ``zope.dottedname``
+ resolution mechanism will be chosen.
+
+ If the ``name`` argument passed to this method is not a string, a
+ :exc:`ValueError` will be raised.
+ """
+ if not isinstance(name, string_types):
+ raise ValueError('%r is not a string' % (name,))
+ return self.maybe_resolve(name)
+
+ def maybe_resolve(self, dotted):
+ """
+ This method behaves just like
+ :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the
+ ``name`` value passed is not a string, it is simply returned. For
+ example:
+
+ .. code-block:: python
+
+ import xml
+ r = DottedNameResolver()
+ v = r.resolve(xml)
+ # v is the xml module; no exception raised
+ """
+ if isinstance(dotted, string_types):
+ if ':' in dotted:
+ return self._pkg_resources_style(dotted)
+ else:
+ return self._zope_dottedname_style(dotted)
+ return dotted
+
+
+ def _pkg_resources_style(self, value):
+ """ package.module:attr style """
+ if value.startswith('.') or value.startswith(':'):
+ if not self.package_name:
+ raise ValueError(
+ 'relative name %r irresolveable without '
+ 'package_name' % (value,))
+ if value in ['.', ':']:
+ value = self.package_name
+ else:
+ value = self.package_name + value
+ return pkg_resources.EntryPoint.parse(
+ 'x=%s' % value).load(False)
+
+ def _zope_dottedname_style(self, value):
+ """ package.module.attr style """
+ module = self.package_name
+ if not module:
+ module = None
+ if value == '.':
+ if module is None:
+ raise ValueError(
+ 'relative name %r irresolveable without package' % (value,)
+ )
+ name = module.split('.')
+ else:
+ name = value.split('.')
+ if not name[0]:
+ if module is None:
+ raise ValueError(
+ 'relative name %r irresolveable without '
+ 'package' % (value,)
+ )
+ module = module.split('.')
+ name.pop(0)
+ while not name[0]:
+ module.pop()
+ name.pop(0)
+ name = module + name
+
+ used = name.pop(0)
+ found = __import__(used)
+ for n in name:
+ used += '.' + n
+ try:
+ found = getattr(found, n)
+ except AttributeError:
+ __import__(used)
+ found = getattr(found, n) # pragma: no cover
+
+ return found
+
+@implementer(IAssetDescriptor)
+class PkgResourcesAssetDescriptor(object):
+ pkg_resources = pkg_resources
+
+ def __init__(self, pkg_name, path):
+ self.pkg_name = pkg_name
+ self.path = path
+
+ def absspec(self):
+ return '%s:%s' % (self.pkg_name, self.path)
+
+ def abspath(self):
+ return self.pkg_resources.resource_filename(self.pkg_name, self.path)
+
+ def stream(self):
+ return self.pkg_resources.resource_stream(self.pkg_name, self.path)
+
+ def isdir(self):
+ return self.pkg_resources.resource_isdir(self.pkg_name, self.path)
+
+ def listdir(self):
+ return self.pkg_resources.resource_listdir(self.pkg_name, self.path)
+
+ def exists(self):
+ return self.pkg_resources.resource_exists(self.pkg_name, self.path)
+
+@implementer(IAssetDescriptor)
+class FSAssetDescriptor(object):
+
+ def __init__(self, path):
+ self.path = os.path.abspath(path)
+
+ def absspec(self):
+ raise NotImplementedError
+
+ def abspath(self):
+ return self.path
+
+ def stream(self):
+ return open(self.path, 'rb')
+
+ def isdir(self):
+ return os.path.isdir(self.path)
+
+ def listdir(self):
+ return os.listdir(self.path)
+
+ def exists(self):
+ return os.path.exists(self.path)
diff --git a/pyramid/tests/test_asset.py b/pyramid/tests/test_asset.py
index badb91d91..d3ebd5f7d 100644
--- a/pyramid/tests/test_asset.py
+++ b/pyramid/tests/test_asset.py
@@ -1,4 +1,7 @@
import unittest
+import os
+
+here = os.path.abspath(os.path.dirname(__file__))
class Test_resolve_asset_spec(unittest.TestCase):
def _callFUT(self, spec, package_name='__main__'):
@@ -6,11 +9,8 @@ class Test_resolve_asset_spec(unittest.TestCase):
return resolve_asset_spec(spec, package_name)
def test_abspath(self):
- import os
- here = os.path.dirname(__file__)
- path = os.path.abspath(here)
- package_name, filename = self._callFUT(path, 'apackage')
- self.assertEqual(filename, path)
+ package_name, filename = self._callFUT(here, 'apackage')
+ self.assertEqual(filename, here)
self.assertEqual(package_name, None)
def test_rel_spec(self):
@@ -57,11 +57,8 @@ class Test_abspath_from_asset_spec(unittest.TestCase):
self.assertEqual(result, '/abc')
def test_pkgrelative(self):
- import os
- here = os.path.dirname(__file__)
- path = os.path.abspath(here)
result = self._callFUT('abc', 'pyramid.tests')
- self.assertEqual(result, os.path.join(path, 'abc'))
+ self.assertEqual(result, os.path.join(here, 'abc'))
class Test_asset_spec_from_abspath(unittest.TestCase):
def _callFUT(self, abspath, package):
@@ -74,20 +71,16 @@ class Test_asset_spec_from_abspath(unittest.TestCase):
self.assertEqual(result, 'abspath')
def test_abspath_startswith_package_path(self):
- import os
- abspath = os.path.join(os.path.dirname(__file__), 'fixtureapp')
+ abspath = os.path.join(here, 'fixtureapp')
pkg = DummyPackage('pyramid.tests')
pkg.__file__ = 'file'
result = self._callFUT(abspath, pkg)
self.assertEqual(result, 'pyramid:fixtureapp')
def test_abspath_doesnt_startwith_package_path(self):
- import os
- abspath = os.path.dirname(__file__)
pkg = DummyPackage('pyramid.tests')
- result = self._callFUT(abspath, pkg)
- self.assertEqual(result, abspath)
-
+ result = self._callFUT(here, pkg)
+ self.assertEqual(result, here)
class DummyPackage:
def __init__(self, name):
diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py
index 29b9baf1f..f99436eb8 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -1,4 +1,8 @@
import unittest
+import os
+from pyramid.compat import PY3
+
+here = os.path.abspath(os.path.dirname(__file__))
class TestCallerPath(unittest.TestCase):
def tearDown(self):
@@ -16,7 +20,6 @@ class TestCallerPath(unittest.TestCase):
def test_pkgrelative(self):
import os
- here = os.path.abspath(os.path.dirname(__file__))
result = self._callFUT('a/b/c')
self.assertEqual(result, os.path.join(here, 'a/b/c'))
@@ -29,7 +32,6 @@ class TestCallerPath(unittest.TestCase):
def test_memoization_success(self):
import os
- here = os.path.abspath(os.path.dirname(__file__))
from pyramid.tests import test_path
result = self._callFUT('a/b/c')
self.assertEqual(result, os.path.join(here, 'a/b/c'))
@@ -167,7 +169,343 @@ class TestPackageName(unittest.TestCase):
import __main__
result = self._callFUT(__main__)
self.assertEqual(result, '__main__')
-
+
+class TestAssetResolver(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.path import AssetResolver
+ return AssetResolver
+
+ def _makeOne(self, package='pyramid.tests'):
+ return self._getTargetClass()(package)
+
+ def test_ctor_as_package(self):
+ import sys
+ tests = sys.modules['pyramid.tests']
+ inst = self._makeOne(tests)
+ self.assertEqual(inst.package_name, 'pyramid.tests')
+ self.assertEqual(inst.package, tests)
+
+ def test_ctor_as_str(self):
+ import sys
+ tests = sys.modules['pyramid.tests']
+ inst = self._makeOne('pyramid.tests')
+ self.assertEqual(inst.package_name, 'pyramid.tests')
+ self.assertEqual(inst.package, tests)
+
+ def test_resolve_abspath(self):
+ from pyramid.path import FSAssetDescriptor
+ inst = self._makeOne(None)
+ r = inst.resolve(os.path.join(here, 'test_asset.py'))
+ self.assertEqual(r.__class__, FSAssetDescriptor)
+ self.failUnless(r.exists())
+
+ def test_resolve_absspec(self):
+ from pyramid.path import PkgResourcesAssetDescriptor
+ inst = self._makeOne(None)
+ r = inst.resolve('pyramid.tests:test_asset.py')
+ self.assertEqual(r.__class__, PkgResourcesAssetDescriptor)
+ self.failUnless(r.exists())
+
+ def test_resolve_relspec_with_pkg(self):
+ from pyramid.path import PkgResourcesAssetDescriptor
+ inst = self._makeOne('pyramid.tests')
+ r = inst.resolve('test_asset.py')
+ self.assertEqual(r.__class__, PkgResourcesAssetDescriptor)
+ self.failUnless(r.exists())
+
+ def test_resolve_relspec_no_package(self):
+ inst = self._makeOne(None)
+ self.assertRaises(ValueError, inst.resolve, 'test_asset.py')
+
+class TestPkgResourcesAssetDescriptor(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.path import PkgResourcesAssetDescriptor
+ return PkgResourcesAssetDescriptor
+
+ def _makeOne(self, pkg='pyramid.tests', path='test_asset.py'):
+ return self._getTargetClass()(pkg, path)
+
+ def test_class_implements(self):
+ from pyramid.interfaces import IAssetDescriptor
+ from zope.interface.verify import verifyClass
+ klass = self._getTargetClass()
+ verifyClass(IAssetDescriptor, klass)
+
+ def test_instance_implements(self):
+ from pyramid.interfaces import IAssetDescriptor
+ from zope.interface.verify import verifyObject
+ inst = self._makeOne()
+ verifyObject(IAssetDescriptor, inst)
+
+ def test_absspec(self):
+ inst = self._makeOne()
+ self.assertEqual(inst.absspec(), 'pyramid.tests:test_asset.py')
+
+ def test_abspath(self):
+ inst = self._makeOne()
+ self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py'))
+
+ def test_stream(self):
+ inst = self._makeOne()
+ inst.pkg_resources = DummyPkgResource()
+ inst.pkg_resources.resource_stream = lambda x, y: '%s:%s' % (x, y)
+ self.assertEqual(inst.stream(),
+ '%s:%s' % ('pyramid.tests', 'test_asset.py'))
+
+ def test_isdir(self):
+ inst = self._makeOne()
+ inst.pkg_resources = DummyPkgResource()
+ inst.pkg_resources.resource_isdir = lambda x, y: '%s:%s' % (x, y)
+ self.assertEqual(inst.isdir(),
+ '%s:%s' % ('pyramid.tests', 'test_asset.py'))
+
+ def test_listdir(self):
+ inst = self._makeOne()
+ inst.pkg_resources = DummyPkgResource()
+ inst.pkg_resources.resource_listdir = lambda x, y: '%s:%s' % (x, y)
+ self.assertEqual(inst.listdir(),
+ '%s:%s' % ('pyramid.tests', 'test_asset.py'))
+
+ def test_exists(self):
+ inst = self._makeOne()
+ inst.pkg_resources = DummyPkgResource()
+ inst.pkg_resources.resource_exists = lambda x, y: '%s:%s' % (x, y)
+ self.assertEqual(inst.exists(),
+ '%s:%s' % ('pyramid.tests', 'test_asset.py'))
+
+class TestFSAssetDescriptor(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.path import FSAssetDescriptor
+ return FSAssetDescriptor
+
+ def _makeOne(self, path=os.path.join(here, 'test_asset.py')):
+ return self._getTargetClass()(path)
+
+ def test_class_implements(self):
+ from pyramid.interfaces import IAssetDescriptor
+ from zope.interface.verify import verifyClass
+ klass = self._getTargetClass()
+ verifyClass(IAssetDescriptor, klass)
+
+ def test_instance_implements(self):
+ from pyramid.interfaces import IAssetDescriptor
+ from zope.interface.verify import verifyObject
+ inst = self._makeOne()
+ verifyObject(IAssetDescriptor, inst)
+
+ def test_absspec(self):
+ inst = self._makeOne()
+ self.assertRaises(NotImplementedError, inst.absspec)
+
+ def test_abspath(self):
+ inst = self._makeOne()
+ self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py'))
+
+ def test_stream(self):
+ inst = self._makeOne()
+ val = inst.stream().read()
+ self.assertTrue(b'asset' in val)
+
+ def test_isdir_False(self):
+ inst = self._makeOne()
+ self.assertFalse(inst.isdir())
+
+ def test_isdir_True(self):
+ inst = self._makeOne(here)
+ self.assertTrue(inst.isdir())
+
+ def test_listdir(self):
+ inst = self._makeOne(here)
+ self.assertTrue(inst.listdir())
+
+ def test_exists(self):
+ inst = self._makeOne()
+ self.assertTrue(inst.exists())
+
+class TestDottedNameResolver(unittest.TestCase):
+ def _makeOne(self, package=None):
+ from pyramid.path import DottedNameResolver
+ return DottedNameResolver(package)
+
+ def config_exc(self, func, *arg, **kw):
+ try:
+ func(*arg, **kw)
+ except ValueError as e:
+ return e
+ else:
+ raise AssertionError('Invalid not raised') # pragma: no cover
+
+ def test_zope_dottedname_style_resolve_builtin(self):
+ typ = self._makeOne()
+ if PY3: # pragma: no cover
+ result = typ._zope_dottedname_style('builtins.str')
+ else:
+ result = typ._zope_dottedname_style('__builtin__.str')
+ self.assertEqual(result, str)
+
+ def test_zope_dottedname_style_resolve_absolute(self):
+ typ = self._makeOne()
+ result = typ._zope_dottedname_style(
+ 'pyramid.tests.test_path.TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test_zope_dottedname_style_irrresolveable_absolute(self):
+ typ = self._makeOne()
+ self.assertRaises(ImportError, typ._zope_dottedname_style,
+ 'pyramid.test_path.nonexisting_name')
+
+ def test__zope_dottedname_style_resolve_relative(self):
+ import pyramid.tests
+ typ = self._makeOne(package=pyramid.tests)
+ result = typ._zope_dottedname_style(
+ '.test_path.TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test__zope_dottedname_style_resolve_relative_leading_dots(self):
+ import pyramid.tests.test_configuration
+ typ = self._makeOne(package=pyramid.tests)
+ result = typ._zope_dottedname_style(
+ '..tests.test_path.TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test__zope_dottedname_style_resolve_relative_is_dot(self):
+ import pyramid.tests
+ typ = self._makeOne(package=pyramid.tests)
+ result = typ._zope_dottedname_style('.')
+ self.assertEqual(result, pyramid.tests)
+
+ def test__zope_dottedname_style_irresolveable_relative_is_dot(self):
+ typ = self._makeOne()
+ e = self.config_exc(typ._zope_dottedname_style, '.')
+ self.assertEqual(
+ e.args[0],
+ "relative name '.' irresolveable without package")
+
+ def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self):
+ typ = self._makeOne()
+ e = self.config_exc(typ._zope_dottedname_style, '.whatever')
+ self.assertEqual(
+ e.args[0],
+ "relative name '.whatever' irresolveable without package")
+
+ def test_zope_dottedname_style_irrresolveable_relative(self):
+ import pyramid.tests
+ typ = self._makeOne(package=pyramid.tests)
+ self.assertRaises(ImportError, typ._zope_dottedname_style,
+ '.notexisting')
+
+ def test__zope_dottedname_style_resolveable_relative(self):
+ import pyramid
+ typ = self._makeOne(package=pyramid)
+ result = typ._zope_dottedname_style('.tests')
+ from pyramid import tests
+ self.assertEqual(result, tests)
+
+ def test__zope_dottedname_style_irresolveable_absolute(self):
+ typ = self._makeOne()
+ self.assertRaises(
+ ImportError,
+ typ._zope_dottedname_style, 'pyramid.fudge.bar')
+
+ def test__zope_dottedname_style_resolveable_absolute(self):
+ typ = self._makeOne()
+ result = typ._zope_dottedname_style(
+ 'pyramid.tests.test_path.TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test__pkg_resources_style_resolve_absolute(self):
+ typ = self._makeOne()
+ result = typ._pkg_resources_style(
+ 'pyramid.tests.test_path:TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test__pkg_resources_style_irrresolveable_absolute(self):
+ typ = self._makeOne()
+ self.assertRaises(ImportError, typ._pkg_resources_style,
+ 'pyramid.tests:nonexisting')
+
+ def test__pkg_resources_style_resolve_relative(self):
+ import pyramid.tests
+ typ = self._makeOne(package=pyramid.tests)
+ result = typ._pkg_resources_style(
+ '.test_path:TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test__pkg_resources_style_resolve_relative_is_dot(self):
+ import pyramid.tests
+ typ = self._makeOne(package=pyramid.tests)
+ result = typ._pkg_resources_style('.')
+ self.assertEqual(result, pyramid.tests)
+
+ def test__pkg_resources_style_resolve_relative_nocurrentpackage(self):
+ typ = self._makeOne()
+ self.assertRaises(ValueError, typ._pkg_resources_style,
+ '.whatever')
+
+ def test__pkg_resources_style_irrresolveable_relative(self):
+ import pyramid
+ typ = self._makeOne(package=pyramid)
+ self.assertRaises(ImportError, typ._pkg_resources_style,
+ ':notexisting')
+
+ def test_resolve_not_a_string(self):
+ typ = self._makeOne()
+ e = self.config_exc(typ.resolve, None)
+ self.assertEqual(e.args[0], 'None is not a string')
+
+ def test_resolve_using_pkgresources_style(self):
+ typ = self._makeOne()
+ result = typ.resolve(
+ 'pyramid.tests.test_path:TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test_resolve_using_zope_dottedname_style(self):
+ typ = self._makeOne()
+ result = typ.resolve(
+ 'pyramid.tests.test_path:TestDottedNameResolver')
+ self.assertEqual(result, self.__class__)
+
+ def test_resolve_missing_raises(self):
+ typ = self._makeOne()
+ self.assertRaises(ImportError, typ.resolve, 'cant.be.found')
+
+ def test_ctor_string_module_resolveable(self):
+ import pyramid.tests
+ typ = self._makeOne('pyramid.tests.test_path')
+ self.assertEqual(typ.package, pyramid.tests)
+ self.assertEqual(typ.package_name, 'pyramid.tests')
+
+ def test_ctor_string_package_resolveable(self):
+ import pyramid.tests
+ typ = self._makeOne('pyramid.tests')
+ self.assertEqual(typ.package, pyramid.tests)
+ self.assertEqual(typ.package_name, 'pyramid.tests')
+
+ def test_ctor_string_irresolveable(self):
+ self.assertRaises(ValueError, self._makeOne, 'cant.be.found')
+
+ def test_ctor_module(self):
+ import pyramid.tests
+ import pyramid.tests.test_path
+ typ = self._makeOne(pyramid.tests.test_path)
+ self.assertEqual(typ.package, pyramid.tests)
+ self.assertEqual(typ.package_name, 'pyramid.tests')
+
+ def test_ctor_package(self):
+ import pyramid.tests
+ typ = self._makeOne(pyramid.tests)
+ self.assertEqual(typ.package, pyramid.tests)
+ self.assertEqual(typ.package_name, 'pyramid.tests')
+
+ def test_ctor_None(self):
+ typ = self._makeOne(None)
+ self.assertEqual(typ.package, None)
+ self.assertEqual(typ.package_name, None)
+
+
+class DummyPkgResource(object):
+ pass
+
class DummyPackageOrModule:
def __init__(self, real_package_or_module, raise_exc=None):
self.__dict__['raise_exc'] = raise_exc
@@ -181,9 +519,3 @@ class DummyPackageOrModule:
if self.raise_exc is not None:
raise self.raise_exc
self.__dict__[key] = val
-
-
-
-
-
-
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index 61e372417..b9a9d1960 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -1,189 +1,6 @@
import unittest
from pyramid.compat import PY3
-class TestDottedNameResolver(unittest.TestCase):
- def _makeOne(self, package=None):
- from pyramid.util import DottedNameResolver
- return DottedNameResolver(package)
-
- def config_exc(self, func, *arg, **kw):
- from pyramid.exceptions import ConfigurationError
- try:
- func(*arg, **kw)
- except ConfigurationError as e:
- return e
- else:
- raise AssertionError('Invalid not raised') # pragma: no cover
-
- def test_zope_dottedname_style_resolve_builtin(self):
- typ = self._makeOne()
- if PY3: # pragma: no cover
- result = typ._zope_dottedname_style('builtins.str')
- else:
- result = typ._zope_dottedname_style('__builtin__.str')
- self.assertEqual(result, str)
-
- def test_zope_dottedname_style_resolve_absolute(self):
- typ = self._makeOne()
- result = typ._zope_dottedname_style(
- 'pyramid.tests.test_util.TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test_zope_dottedname_style_irrresolveable_absolute(self):
- typ = self._makeOne()
- self.assertRaises(ImportError, typ._zope_dottedname_style,
- 'pyramid.test_util.nonexisting_name')
-
- def test__zope_dottedname_style_resolve_relative(self):
- import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
- result = typ._zope_dottedname_style(
- '.test_util.TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test__zope_dottedname_style_resolve_relative_leading_dots(self):
- import pyramid.tests.test_configuration
- typ = self._makeOne(package=pyramid.tests)
- result = typ._zope_dottedname_style(
- '..tests.test_util.TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test__zope_dottedname_style_resolve_relative_is_dot(self):
- import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
- result = typ._zope_dottedname_style('.')
- self.assertEqual(result, pyramid.tests)
-
- def test__zope_dottedname_style_irresolveable_relative_is_dot(self):
- typ = self._makeOne()
- e = self.config_exc(typ._zope_dottedname_style, '.')
- self.assertEqual(
- e.args[0],
- "relative name '.' irresolveable without package")
-
- def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self):
- typ = self._makeOne()
- e = self.config_exc(typ._zope_dottedname_style, '.whatever')
- self.assertEqual(
- e.args[0],
- "relative name '.whatever' irresolveable without package")
-
- def test_zope_dottedname_style_irrresolveable_relative(self):
- import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
- self.assertRaises(ImportError, typ._zope_dottedname_style,
- '.notexisting')
-
- def test__zope_dottedname_style_resolveable_relative(self):
- import pyramid
- typ = self._makeOne(package=pyramid)
- result = typ._zope_dottedname_style('.tests')
- from pyramid import tests
- self.assertEqual(result, tests)
-
- def test__zope_dottedname_style_irresolveable_absolute(self):
- typ = self._makeOne()
- self.assertRaises(
- ImportError,
- typ._zope_dottedname_style, 'pyramid.fudge.bar')
-
- def test__zope_dottedname_style_resolveable_absolute(self):
- typ = self._makeOne()
- result = typ._zope_dottedname_style(
- 'pyramid.tests.test_util.TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test__pkg_resources_style_resolve_absolute(self):
- typ = self._makeOne()
- result = typ._pkg_resources_style(
- 'pyramid.tests.test_util:TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test__pkg_resources_style_irrresolveable_absolute(self):
- typ = self._makeOne()
- self.assertRaises(ImportError, typ._pkg_resources_style,
- 'pyramid.tests:nonexisting')
-
- def test__pkg_resources_style_resolve_relative(self):
- import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
- result = typ._pkg_resources_style(
- '.test_util:TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test__pkg_resources_style_resolve_relative_is_dot(self):
- import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
- result = typ._pkg_resources_style('.')
- self.assertEqual(result, pyramid.tests)
-
- def test__pkg_resources_style_resolve_relative_nocurrentpackage(self):
- typ = self._makeOne()
- from pyramid.exceptions import ConfigurationError
- self.assertRaises(ConfigurationError, typ._pkg_resources_style,
- '.whatever')
-
- def test__pkg_resources_style_irrresolveable_relative(self):
- import pyramid
- typ = self._makeOne(package=pyramid)
- self.assertRaises(ImportError, typ._pkg_resources_style,
- ':notexisting')
-
- def test_resolve_not_a_string(self):
- typ = self._makeOne()
- e = self.config_exc(typ.resolve, None)
- self.assertEqual(e.args[0], 'None is not a string')
-
- def test_resolve_using_pkgresources_style(self):
- typ = self._makeOne()
- result = typ.resolve(
- 'pyramid.tests.test_util:TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test_resolve_using_zope_dottedname_style(self):
- typ = self._makeOne()
- result = typ.resolve(
- 'pyramid.tests.test_util:TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test_resolve_missing_raises(self):
- typ = self._makeOne()
- self.assertRaises(ImportError, typ.resolve, 'cant.be.found')
-
- def test_ctor_string_module_resolveable(self):
- import pyramid.tests
- typ = self._makeOne('pyramid.tests.test_util')
- self.assertEqual(typ.package, pyramid.tests)
- self.assertEqual(typ.package_name, 'pyramid.tests')
-
- def test_ctor_string_package_resolveable(self):
- import pyramid.tests
- typ = self._makeOne('pyramid.tests')
- self.assertEqual(typ.package, pyramid.tests)
- self.assertEqual(typ.package_name, 'pyramid.tests')
-
- def test_ctor_string_irresolveable(self):
- from pyramid.config import ConfigurationError
- self.assertRaises(ConfigurationError, self._makeOne, 'cant.be.found')
-
- def test_ctor_module(self):
- import pyramid.tests
- import pyramid.tests.test_util
- typ = self._makeOne(pyramid.tests.test_util)
- self.assertEqual(typ.package, pyramid.tests)
- self.assertEqual(typ.package_name, 'pyramid.tests')
-
- def test_ctor_package(self):
- import pyramid.tests
- typ = self._makeOne(pyramid.tests)
- self.assertEqual(typ.package, pyramid.tests)
- self.assertEqual(typ.package_name, 'pyramid.tests')
-
- def test_ctor_None(self):
- typ = self._makeOne(None)
- self.assertEqual(typ.package, None)
- self.assertEqual(typ.package_name, None)
-
class Test_WeakOrderedSet(unittest.TestCase):
def _makeOne(self):
from pyramid.config import WeakOrderedSet
diff --git a/pyramid/util.py b/pyramid/util.py
index f22f847c4..2018fb73e 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -1,6 +1,4 @@
import inspect
-import pkg_resources
-import sys
import weakref
from pyramid.compat import (
@@ -10,147 +8,9 @@ from pyramid.compat import (
PY3,
)
-from pyramid.exceptions import ConfigurationError
-from pyramid.path import package_of
+from pyramid.path import DottedNameResolver # bw compat
-class DottedNameResolver(object):
- """ This class resolves dotted name references to 'global' Python
- objects (objects which can be imported) to those objects.
-
- Two dotted name styles are supported during deserialization:
-
- - ``pkg_resources``-style dotted names where non-module attributes
- of a package are separated from the rest of the path using a ':'
- e.g. ``package.module:attr``.
-
- - ``zope.dottedname``-style dotted names where non-module
- attributes of a package are separated from the rest of the path
- using a '.' e.g. ``package.module.attr``.
-
- These styles can be used interchangeably. If the serialization
- contains a ``:`` (colon), the ``pkg_resources`` resolution
- mechanism will be chosen, otherwise the ``zope.dottedname``
- resolution mechanism will be chosen.
-
- The constructor accepts a single argument named ``package`` which
- should be a one of:
-
- - a Python module or package object
-
- - A fully qualified (not relative) dotted name to a module or package
-
- - The value ``None``
-
- The ``package`` is used when relative dotted names are supplied to
- the resolver's ``resolve`` and ``maybe_resolve`` methods. A
- dotted name which has a ``.`` (dot) or ``:`` (colon) as its first
- character is treated as relative.
-
- If the value ``None`` is supplied as the package name, the
- resolver will only be able to resolve fully qualified (not
- relative) names. Any attempt to resolve a relative name when the
- ``package`` is ``None`` will result in an
- :exc:`pyramid.config.ConfigurationError` exception.
-
- If a *module* or *module name* (as opposed to a package or package
- name) is supplied as ``package``, its containing package is
- computed and this package used to derive the package name (all
- names are resolved relative to packages, never to modules). For
- example, if the ``package`` argument to this type was passed the
- string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to
- the ``resolve`` method, the resulting import would be for
- ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module
- object, not a package object.
-
- If a *package* or *package name* (as opposed to a module or module
- name) is supplied as ``package``, this package will be used to
- relative compute dotted names. For example, if the ``package``
- argument to this type was passed the string ``xml.dom``, and
- ``.minidom`` is supplied to the ``resolve`` method, the resulting
- import would be for ``xml.minidom``.
-
- When a dotted name cannot be resolved, a
- :class:`pyramid.exceptions.ConfigurationError` error is raised.
- """
- def __init__(self, package):
- if package is None:
- self.package_name = None
- self.package = None
- else:
- if isinstance(package, string_types):
- try:
- __import__(package)
- except ImportError:
- raise ConfigurationError(
- 'The dotted name %r cannot be imported' % (package,))
- package = sys.modules[package]
- self.package = package_of(package)
- self.package_name = self.package.__name__
-
- def _pkg_resources_style(self, value):
- """ package.module:attr style """
- if value.startswith('.') or value.startswith(':'):
- if not self.package_name:
- raise ConfigurationError(
- 'relative name %r irresolveable without '
- 'package_name' % (value,))
- if value in ['.', ':']:
- value = self.package_name
- else:
- value = self.package_name + value
- return pkg_resources.EntryPoint.parse(
- 'x=%s' % value).load(False)
-
- def _zope_dottedname_style(self, value):
- """ package.module.attr style """
- module = self.package_name
- if not module:
- module = None
- if value == '.':
- if module is None:
- raise ConfigurationError(
- 'relative name %r irresolveable without package' % (value,)
- )
- name = module.split('.')
- else:
- name = value.split('.')
- if not name[0]:
- if module is None:
- raise ConfigurationError(
- 'relative name %r irresolveable without '
- 'package' % (value,)
- )
- module = module.split('.')
- name.pop(0)
- while not name[0]:
- module.pop()
- name.pop(0)
- name = module + name
-
- used = name.pop(0)
- found = __import__(used)
- for n in name:
- used += '.' + n
- try:
- found = getattr(found, n)
- except AttributeError:
- __import__(used)
- found = getattr(found, n) # pragma: no cover
-
- return found
-
- def resolve(self, dotted):
- if not isinstance(dotted, string_types):
- raise ConfigurationError('%r is not a string' % (dotted,))
- return self.maybe_resolve(dotted)
-
- def maybe_resolve(self, dotted):
- if isinstance(dotted, string_types):
- if ':' in dotted:
- return self._pkg_resources_style(dotted)
- else:
- return self._zope_dottedname_style(dotted)
- return dotted
+DottedNameResolver = DottedNameResolver # for pyflakes
class WeakOrderedSet(object):
""" Maintain a set of items.