diff --git a/src/pyramid/asset.py b/src/pyramid/asset.py index bdf90ef5e..75c577e51 100644 --- a/src/pyramid/asset.py +++ b/src/pyramid/asset.py @@ -1,7 +1,6 @@ import os -import pkg_resources -from pyramid.path import package_name, package_path +from pyramid.path import package_name, package_path, resource_filename def resolve_asset_spec(spec, pname='__main__'): @@ -40,4 +39,4 @@ def abspath_from_asset_spec(spec, pname='__main__'): pname, filename = resolve_asset_spec(spec, pname) if pname is None: return filename - return pkg_resources.resource_filename(pname, filename) + return resource_filename(pname, filename) diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index a49c15cba..1d48221d9 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -36,8 +36,9 @@ IDebugLogger, IExceptionResponse, ) -from pyramid.path import DottedNameResolver, caller_package, package_of +from pyramid.path import caller_package, package_of from pyramid.registry import Introspectable, Introspector, Registry +from pyramid.resolver import DottedNameResolver from pyramid.router import Router from pyramid.settings import aslist from pyramid.threadlocal import manager diff --git a/src/pyramid/config/i18n.py b/src/pyramid/config/i18n.py index d70a8f25a..089834671 100644 --- a/src/pyramid/config/i18n.py +++ b/src/pyramid/config/i18n.py @@ -1,7 +1,7 @@ from pyramid.config.actions import action_method from pyramid.exceptions import ConfigurationError from pyramid.interfaces import ILocaleNegotiator, ITranslationDirectories -from pyramid.path import AssetResolver +from pyramid.resolver import AssetResolver class I18NConfiguratorMixin: diff --git a/src/pyramid/path.py b/src/pyramid/path.py index 160d97abe..3bc87b5f3 100644 --- a/src/pyramid/path.py +++ b/src/pyramid/path.py @@ -1,6 +1,8 @@ +import atexit +from contextlib import ExitStack import functools -from importlib import import_module from importlib.machinery import SOURCE_SUFFIXES +import importlib.resources import os import pkg_resources import sys @@ -11,6 +13,30 @@ init_names = ['__init__%s' % x for x in SOURCE_SUFFIXES] +@functools.lru_cache(maxsize=None) +def resource_filename(package, name): + """ + Return a filename on the filesystem for the given resource. If the + resource does not exist in the filesystem (e.g. in a zipped egg), it will + be extracted to a temporary directory and cleaned up when the application + exits. + + This function is equivalent to the now-deprecated + ``pkg_resources.resource_filename``. + + This function is only included in order to provide legacy functionality; + use should be avoided. Instead prefer to use ``importlib.resource`` APIs + directly. + + """ + ref = importlib.resources.files(package) / name + + manager = ExitStack() + atexit.register(manager.close) + path = manager.enter_context(importlib.resources.as_file(ref)) + return str(path) + + def caller_path(path, level=2): if not os.path.isabs(path): module = caller_module(level + 1) @@ -70,7 +96,7 @@ def package_path(package): # the result prefix = getattr(package, '__abspath__', None) if prefix is None: - prefix = pkg_resources.resource_filename(package.__name__, '') + prefix = resource_filename(package.__name__, '') # pkg_resources doesn't care whether we feed it a package # name or a module name within the package, the result # will be the same: a directory name to the package itself @@ -90,340 +116,56 @@ def __repr__(self): # pragma: no cover (for docs) CALLER_PACKAGE = _CALLER_PACKAGE() -class Resolver: - def __init__(self, package=CALLER_PACKAGE): - if package in (None, CALLER_PACKAGE): - self.package = package - else: - if isinstance(package, str): - try: - __import__(package) - except ImportError: - raise ValueError( - f'The dotted name {package!r} cannot be imported' - ) - package = sys.modules[package] - self.package = package_of(package) - - def get_package_name(self): - if self.package is CALLER_PACKAGE: - package_name = caller_package().__name__ - else: - package_name = self.package.__name__ - return package_name - - def get_package(self): - if self.package is CALLER_PACKAGE: - package = caller_package() - else: - package = self.package - return package - - -class AssetResolver(Resolver): - """A class used to resolve an :term:`asset specification` to an - :term:`asset descriptor`. - - .. versionadded:: 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 constant value :attr:`pyramid.path.CALLER_PACKAGE`. - - The default value is :attr:`pyramid.path.CALLER_PACKAGE`. - - 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 ``package`` is ``None``, the resolver will - only be able to resolve fully qualified (not relative) asset - specifications. Any attempt to resolve a relative asset specification - will result in an :exc:`ValueError` exception. - - If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, - the resolver will treat relative asset specifications as - relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve` - method. - - If ``package`` is a *module* or *module name* (as opposed to a package or - package name), its containing package is computed and this - package is 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 ``package`` is a *package* or *package name* (as opposed to a module or - module name), 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 - :class:`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 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 of - ``None``, and a relative asset specification is passed to - ``resolve``, an :exc:`ValueError` exception is raised. - """ - if os.path.isabs(spec): - return FSAssetDescriptor(spec) - path = spec - if ':' in path: - package_name, path = spec.split(':', 1) - else: - if self.package is CALLER_PACKAGE: - package_name = caller_package().__name__ - else: - package_name = getattr(self.package, '__name__', None) - if package_name is None: - raise ValueError( - f'relative spec {spec!r} irresolveable without package' - ) - return PkgResourcesAssetDescriptor(package_name, path) - - -class DottedNameResolver(Resolver): - """A class used to resolve a :term:`dotted Python name` to a package or - module object. - - .. versionadded:: 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 constant value :attr:`pyramid.path.CALLER_PACKAGE`. - - The default value is :attr:`pyramid.path.CALLER_PACKAGE`. - - 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 ``package`` is ``None``, the resolver will only be able to resolve - fully qualified (not relative) names. Any attempt to resolve a - relative name will result in an :exc:`ValueError` exception. - - If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, - the resolver will treat relative dotted names as relative to - the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve` - method. - - If ``package`` is a *module* or *module name* (as opposed to a package or - package name), 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 ``package`` is a *package* or *package name* (as opposed to a module or - module name), 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``. - """ - - def resolve(self, dotted): - """ - 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 ``dotted`` argument passed to this method is not a string, a - :exc:`ValueError` will be raised. - - When a dotted name cannot be resolved, a :exc:`ValueError` error is - raised. - - Example: - - .. code-block:: python - - r = DottedNameResolver() - v = r.resolve('xml') # v is the xml module - - """ - if not isinstance(dotted, str): - raise ValueError(f'{dotted!r} is not a string') - package = self.package - if package is CALLER_PACKAGE: - package = caller_package() - return self._resolve(dotted, package) - - def maybe_resolve(self, dotted): - """ - This method behaves just like - :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the - ``dotted`` value passed is not a string, it is simply returned. For - example: - - .. code-block:: python - - import xml - r = DottedNameResolver() - v = r.maybe_resolve(xml) - # v is the xml module; no exception raised - """ - if isinstance(dotted, str): - package = self.package - if package is CALLER_PACKAGE: - package = caller_package() - return self._resolve(dotted, package) - return dotted - - def _resolve(self, dotted, package): - if ':' in dotted: - return self._pkg_resources_style(dotted, package) - else: - return self._zope_dottedname_style(dotted, package) - - def _pkg_resources_style(self, value, package): - """package.module:attr style""" - if value.startswith(('.', ':')): - if not package: - raise ValueError( - f'relative name {value!r} irresolveable without package' - ) - if value in ['.', ':']: - value = package.__name__ - else: - value = package.__name__ + value - # logic below is similar to importlib.metadata.EntryPoint.load() - module = value - attrs = [] - parts = value.split(':', 1) - if len(parts) == 2: - module, attrs = parts - attrs = attrs.split('.') - module = import_module(module) - try: - return functools.reduce(getattr, attrs, module) - except AttributeError as ex: - raise ImportError(str(ex)) - - def _zope_dottedname_style(self, value, package): - """package.module.attr style""" - module = getattr(package, '__name__', None) # package may be None - if not module: - module = None - if value == '.': - if module is None: - raise ValueError( - f'relative name {value!r} irresolveable without package' - ) - 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: pkg_resources = pkg_resources - def __init__(self, pkg_name, path): + def __init__(self, pkg_name, path, overrides=None): self.pkg_name = pkg_name self.path = path + self.overrides = overrides def absspec(self): return f'{self.pkg_name}:{self.path}' def abspath(self): - return os.path.abspath( - self.pkg_resources.resource_filename(self.pkg_name, self.path) - ) + if self.overrides is not None: + filename = self.overrides.get_filename(self.path) + else: + filename = None + if filename is None: + filename = self.pkg_resources.resource_filename( + self.pkg_name, + self.path, + ) + return os.path.abspath(filename) def stream(self): + if self.overrides is not None: + stream = self.overrides.get_stream(self.path) + if stream is not None: + return stream return self.pkg_resources.resource_stream(self.pkg_name, self.path) def isdir(self): + if self.overrides is not None: + result = self.overrides.isdir(self.path) + if result is not None: + return result return self.pkg_resources.resource_isdir(self.pkg_name, self.path) def listdir(self): + if self.overrides is not None: + result = self.overrides.listdir(self.path) + if result is not None: + return result return self.pkg_resources.resource_listdir(self.pkg_name, self.path) def exists(self): + if self.overrides is not None: + result = self.overrides.exists(self.path) + if result is not None: + return result return self.pkg_resources.resource_exists(self.pkg_name, self.path) @@ -449,3 +191,9 @@ def listdir(self): def exists(self): return os.path.exists(self.path) + + +# We're importing these classes for backwards compatibility, because these used +# to exist in `pyramid.path`. +# This must be at the bottom of the file to avoid a circular import. +from .resolver import AssetResolver, DottedNameResolver, Resolver # noqa diff --git a/src/pyramid/resolver.py b/src/pyramid/resolver.py new file mode 100644 index 000000000..6792540f4 --- /dev/null +++ b/src/pyramid/resolver.py @@ -0,0 +1,330 @@ +import functools +from importlib import import_module +import os +import sys + +from pyramid.interfaces import IPackageOverrides + +from .path import ( + CALLER_PACKAGE, + FSAssetDescriptor, + PkgResourcesAssetDescriptor, + caller_package, + package_of, +) +from .threadlocal import get_current_registry + + +class Resolver: + def __init__(self, package=CALLER_PACKAGE): + if package in (None, CALLER_PACKAGE): + self.package = package + else: + if isinstance(package, str): + try: + __import__(package) + except ImportError: + raise ValueError( + f'The dotted name {package!r} cannot be imported' + ) + package = sys.modules[package] + self.package = package_of(package) + + def get_package_name(self): + if self.package is CALLER_PACKAGE: + package_name = caller_package().__name__ + else: + package_name = self.package.__name__ + return package_name + + def get_package(self): + if self.package is CALLER_PACKAGE: + package = caller_package() + else: + package = self.package + return package + + +class AssetResolver(Resolver): + """A class used to resolve an :term:`asset specification` to an + :term:`asset descriptor`. + + .. versionadded:: 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 constant value :attr:`pyramid.path.CALLER_PACKAGE`. + + The default value is :attr:`pyramid.path.CALLER_PACKAGE`. + + 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 ``package`` is ``None``, the resolver will + only be able to resolve fully qualified (not relative) asset + specifications. Any attempt to resolve a relative asset specification + will result in an :exc:`ValueError` exception. + + If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, + the resolver will treat relative asset specifications as + relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve` + method. + + If ``package`` is a *module* or *module name* (as opposed to a package or + package name), its containing package is computed and this + package is 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 ``package`` is a *package* or *package name* (as opposed to a module or + module name), 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 __init__(self, package=CALLER_PACKAGE, registry=None): + self.registry = registry + super().__init__(package=package) + + def resolve(self, spec): + """ + Resolve the asset spec named as ``spec`` to an object that has the + attributes and methods described in + :class:`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 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 of + ``None``, and a relative asset specification is passed to + ``resolve``, an :exc:`ValueError` exception is raised. + """ + if os.path.isabs(spec): + return FSAssetDescriptor(spec) + path = spec + if ':' in path: + package_name, path = spec.split(':', 1) + else: + if self.package is CALLER_PACKAGE: + package_name = caller_package().__name__ + else: + package_name = getattr(self.package, '__name__', None) + if package_name is None: + raise ValueError( + f'relative spec {spec!r} irresolveable without package' + ) + + registry = self.registry or get_current_registry() + overrides = registry.queryUtility(IPackageOverrides, package_name) + return PkgResourcesAssetDescriptor(package_name, path, overrides) + + +class DottedNameResolver(Resolver): + """A class used to resolve a :term:`dotted Python name` to a package or + module object. + + .. versionadded:: 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 constant value :attr:`pyramid.path.CALLER_PACKAGE`. + + The default value is :attr:`pyramid.path.CALLER_PACKAGE`. + + 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 ``package`` is ``None``, the resolver will only be able to resolve + fully qualified (not relative) names. Any attempt to resolve a + relative name will result in an :exc:`ValueError` exception. + + If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, + the resolver will treat relative dotted names as relative to + the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve` + method. + + If ``package`` is a *module* or *module name* (as opposed to a package or + package name), 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 ``package`` is a *package* or *package name* (as opposed to a module or + module name), 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``. + """ + + def resolve(self, dotted): + """ + 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 ``dotted`` argument passed to this method is not a string, a + :exc:`ValueError` will be raised. + + When a dotted name cannot be resolved, a :exc:`ValueError` error is + raised. + + Example: + + .. code-block:: python + + r = DottedNameResolver() + v = r.resolve('xml') # v is the xml module + + """ + if not isinstance(dotted, str): + raise ValueError(f'{dotted!r} is not a string') + package = self.package + if package is CALLER_PACKAGE: + package = caller_package() + return self._resolve(dotted, package) + + def maybe_resolve(self, dotted): + """ + This method behaves just like + :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the + ``dotted`` value passed is not a string, it is simply returned. For + example: + + .. code-block:: python + + import xml + r = DottedNameResolver() + v = r.maybe_resolve(xml) + # v is the xml module; no exception raised + """ + if isinstance(dotted, str): + package = self.package + if package is CALLER_PACKAGE: + package = caller_package() + return self._resolve(dotted, package) + return dotted + + def _resolve(self, dotted, package): + if ':' in dotted: + return self._pkg_resources_style(dotted, package) + else: + return self._zope_dottedname_style(dotted, package) + + def _pkg_resources_style(self, value, package): + """package.module:attr style""" + if value.startswith(('.', ':')): + if not package: + raise ValueError( + f'relative name {value!r} irresolveable without package' + ) + if value in ['.', ':']: + value = package.__name__ + else: + value = package.__name__ + value + # logic below is similar to importlib.metadata.EntryPoint.load() + module = value + attrs = [] + parts = value.split(':', 1) + if len(parts) == 2: + module, attrs = parts + attrs = attrs.split('.') + module = import_module(module) + try: + return functools.reduce(getattr, attrs, module) + except AttributeError as ex: + raise ImportError(str(ex)) + + def _zope_dottedname_style(self, value, package): + """package.module.attr style""" + module = getattr(package, '__name__', None) # package may be None + if not module: + module = None + if value == '.': + if module is None: + raise ValueError( + f'relative name {value!r} irresolveable without package' + ) + 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 diff --git a/src/pyramid/util.py b/src/pyramid/util.py index c71528a49..5a7169ef7 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -5,7 +5,7 @@ import platform import weakref -from pyramid.path import DottedNameResolver as _DottedNameResolver +from pyramid.resolver import DottedNameResolver as _DottedNameResolver _marker = object() diff --git a/tests/pkgs/assets/__init__.py b/tests/pkgs/assets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/pkgs/assets/dir/fizz.txt b/tests/pkgs/assets/dir/fizz.txt new file mode 100644 index 000000000..99f1ea4a2 --- /dev/null +++ b/tests/pkgs/assets/dir/fizz.txt @@ -0,0 +1 @@ +buzz diff --git a/tests/pkgs/assets/foo.txt b/tests/pkgs/assets/foo.txt new file mode 100644 index 000000000..5716ca598 --- /dev/null +++ b/tests/pkgs/assets/foo.txt @@ -0,0 +1 @@ +bar diff --git a/tests/test_path.py b/tests/test_path.py index 6f12303f7..f93b0ae90 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -4,6 +4,18 @@ here = os.path.abspath(os.path.dirname(__file__)) +class TestResourceFilename(unittest.TestCase): + def _callFUT(self, package, name): + from pyramid.path import resource_filename + + return resource_filename(package, name) + + def test_returns_path(self): + path = self._callFUT('tests.pkgs.assets', 'foo.txt') + expected = os.path.join(here, 'pkgs/assets/foo.txt') + self.assertEqual(path, expected) + + class TestCallerPath(unittest.TestCase): def tearDown(self): from . import test_path @@ -214,97 +226,6 @@ def test_it_main(self): self.assertEqual(result, '__main__') -class TestResolver(unittest.TestCase): - def _getTargetClass(self): - from pyramid.path import Resolver - - return Resolver - - def _makeOne(self, package): - return self._getTargetClass()(package) - - def test_get_package_caller_package(self): - from pyramid.path import CALLER_PACKAGE - import tests - - self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package(), tests) - - def test_get_package_name_caller_package(self): - from pyramid.path import CALLER_PACKAGE - - self.assertEqual( - self._makeOne(CALLER_PACKAGE).get_package_name(), 'tests' - ) - - def test_get_package_string(self): - import tests - - self.assertEqual(self._makeOne('tests').get_package(), tests) - - def test_get_package_name_string(self): - self.assertEqual(self._makeOne('tests').get_package_name(), 'tests') - - -class TestAssetResolver(unittest.TestCase): - def _getTargetClass(self): - from pyramid.path import AssetResolver - - return AssetResolver - - def _makeOne(self, package='tests'): - return self._getTargetClass()(package) - - def test_ctor_as_package(self): - import sys - - tests = sys.modules['tests'] - inst = self._makeOne(tests) - self.assertEqual(inst.package, tests) - - def test_ctor_as_str(self): - import sys - - tests = sys.modules['tests'] - inst = self._makeOne('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.assertTrue(r.exists()) - - def test_resolve_absspec(self): - from pyramid.path import PkgResourcesAssetDescriptor - - inst = self._makeOne(None) - r = inst.resolve('tests:test_asset.py') - self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) - self.assertTrue(r.exists()) - - def test_resolve_relspec_with_pkg(self): - from pyramid.path import PkgResourcesAssetDescriptor - - inst = self._makeOne('tests') - r = inst.resolve('test_asset.py') - self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) - self.assertTrue(r.exists()) - - def test_resolve_relspec_no_package(self): - inst = self._makeOne(None) - self.assertRaises(ValueError, inst.resolve, 'test_asset.py') - - def test_resolve_relspec_caller_package(self): - from pyramid.path import CALLER_PACKAGE, PkgResourcesAssetDescriptor - - inst = self._makeOne(CALLER_PACKAGE) - r = inst.resolve('test_asset.py') - self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) - self.assertTrue(r.exists()) - - class TestPkgResourcesAssetDescriptor(unittest.TestCase): def _getTargetClass(self): from pyramid.path import PkgResourcesAssetDescriptor @@ -423,222 +344,6 @@ def test_exists(self): 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() - result = typ._zope_dottedname_style('builtins.str', None) - self.assertEqual(result, str) - - def test_zope_dottedname_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - 'tests.test_path.TestDottedNameResolver', None - ) - 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', - None, - ) - - def test__zope_dottedname_style_resolve_relative(self): - import tests - - typ = self._makeOne() - result = typ._zope_dottedname_style( - '.test_path.TestDottedNameResolver', tests - ) - self.assertEqual(result, self.__class__) - - def test__zope_dottedname_style_resolve_relative_leading_dots(self): - import tests.test_path - - typ = self._makeOne() - result = typ._zope_dottedname_style( - '..tests.test_path.TestDottedNameResolver', tests - ) - self.assertEqual(result, self.__class__) - - def test__zope_dottedname_style_resolve_relative_is_dot(self): - import tests - - typ = self._makeOne() - result = typ._zope_dottedname_style('.', tests) - self.assertEqual(result, tests) - - def test__zope_dottedname_style_irresolveable_relative_is_dot(self): - typ = self._makeOne() - e = self.config_exc(typ._zope_dottedname_style, '.', None) - 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', None) - self.assertEqual( - e.args[0], - "relative name '.whatever' irresolveable without package", - ) - - def test_zope_dottedname_style_irrresolveable_relative(self): - import tests - - typ = self._makeOne() - self.assertRaises( - ImportError, typ._zope_dottedname_style, '.notexisting', tests - ) - - def test__zope_dottedname_style_resolveable_relative(self): - import tests - - typ = self._makeOne() - result = typ._zope_dottedname_style('.', 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', None - ) - - def test__zope_dottedname_style_resolveable_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - 'tests.test_path.TestDottedNameResolver', None - ) - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._pkg_resources_style( - 'tests.test_path:TestDottedNameResolver', None - ) - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_irrresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises( - ImportError, typ._pkg_resources_style, 'tests:nonexisting', None - ) - - def test__pkg_resources_style_resolve_relative(self): - import tests - - typ = self._makeOne() - result = typ._pkg_resources_style( - '.test_path:TestDottedNameResolver', tests - ) - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_resolve_relative_is_dot(self): - import tests - - typ = self._makeOne() - result = typ._pkg_resources_style('.', tests) - self.assertEqual(result, tests) - - def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): - typ = self._makeOne() - self.assertRaises( - ValueError, typ._pkg_resources_style, '.whatever', None - ) - - def test__pkg_resources_style_irrresolveable_relative(self): - import pyramid - - typ = self._makeOne() - self.assertRaises( - ImportError, typ._pkg_resources_style, ':notexisting', pyramid - ) - - 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('tests.test_path:TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test_resolve_using_zope_dottedname_style(self): - typ = self._makeOne() - result = typ.resolve('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_resolve_caller_package(self): - from pyramid.path import CALLER_PACKAGE - - typ = self._makeOne(CALLER_PACKAGE) - self.assertEqual( - typ.resolve('.test_path.TestDottedNameResolver'), self.__class__ - ) - - def test_maybe_resolve_caller_package(self): - from pyramid.path import CALLER_PACKAGE - - typ = self._makeOne(CALLER_PACKAGE) - self.assertEqual( - typ.maybe_resolve('.test_path.TestDottedNameResolver'), - self.__class__, - ) - - def test_ctor_string_module_resolveable(self): - import tests - - typ = self._makeOne('tests.test_path') - self.assertEqual(typ.package, tests) - - def test_ctor_string_package_resolveable(self): - import tests - - typ = self._makeOne('tests') - self.assertEqual(typ.package, tests) - - def test_ctor_string_irresolveable(self): - self.assertRaises(ValueError, self._makeOne, 'cant.be.found') - - def test_ctor_module(self): - import tests - - from . import test_path - - typ = self._makeOne(test_path) - self.assertEqual(typ.package, tests) - - def test_ctor_package(self): - import tests - - typ = self._makeOne(tests) - self.assertEqual(typ.package, tests) - - def test_ctor_None(self): - typ = self._makeOne(None) - self.assertEqual(typ.package, None) - - class DummyPkgResource: pass diff --git a/tests/test_resolver.py b/tests/test_resolver.py new file mode 100644 index 000000000..77800a74a --- /dev/null +++ b/tests/test_resolver.py @@ -0,0 +1,312 @@ +import os +import unittest + +here = os.path.abspath(os.path.dirname(__file__)) + + +class TestResolver(unittest.TestCase): + def _getTargetClass(self): + from pyramid.resolver import Resolver + + return Resolver + + def _makeOne(self, package): + return self._getTargetClass()(package) + + def test_get_package_caller_package(self): + from pyramid.path import CALLER_PACKAGE + import tests + + self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package(), tests) + + def test_get_package_name_caller_package(self): + from pyramid.path import CALLER_PACKAGE + + self.assertEqual( + self._makeOne(CALLER_PACKAGE).get_package_name(), 'tests' + ) + + def test_get_package_string(self): + import tests + + self.assertEqual(self._makeOne('tests').get_package(), tests) + + def test_get_package_name_string(self): + self.assertEqual(self._makeOne('tests').get_package_name(), 'tests') + + +class TestAssetResolver(unittest.TestCase): + def _getTargetClass(self): + from pyramid.resolver import AssetResolver + + return AssetResolver + + def _makeOne(self, package='tests'): + return self._getTargetClass()(package) + + def test_ctor_as_package(self): + import sys + + tests = sys.modules['tests'] + inst = self._makeOne(tests) + self.assertEqual(inst.package, tests) + + def test_ctor_as_str(self): + import sys + + tests = sys.modules['tests'] + inst = self._makeOne('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.assertTrue(r.exists()) + + def test_resolve_absspec(self): + from pyramid.path import PkgResourcesAssetDescriptor + + inst = self._makeOne(None) + r = inst.resolve('tests:test_asset.py') + self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) + self.assertTrue(r.exists()) + + def test_resolve_relspec_with_pkg(self): + from pyramid.path import PkgResourcesAssetDescriptor + + inst = self._makeOne('tests') + r = inst.resolve('test_asset.py') + self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) + self.assertTrue(r.exists()) + + def test_resolve_relspec_no_package(self): + inst = self._makeOne(None) + self.assertRaises(ValueError, inst.resolve, 'test_asset.py') + + def test_resolve_relspec_caller_package(self): + from pyramid.path import CALLER_PACKAGE, PkgResourcesAssetDescriptor + + inst = self._makeOne(CALLER_PACKAGE) + r = inst.resolve('test_asset.py') + self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) + self.assertTrue(r.exists()) + + +class TestDottedNameResolver(unittest.TestCase): + def _makeOne(self, package=None): + from pyramid.resolver 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() + result = typ._zope_dottedname_style('builtins.str', None) + self.assertEqual(result, str) + + def test_zope_dottedname_style_resolve_absolute(self): + typ = self._makeOne() + result = typ._zope_dottedname_style( + 'tests.test_resolver.TestDottedNameResolver', None + ) + 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_resolver.nonexisting_name', + None, + ) + + def test__zope_dottedname_style_resolve_relative(self): + import tests + + typ = self._makeOne() + result = typ._zope_dottedname_style( + '.test_resolver.TestDottedNameResolver', tests + ) + self.assertEqual(result, self.__class__) + + def test__zope_dottedname_style_resolve_relative_leading_dots(self): + import tests.test_resolver + + typ = self._makeOne() + result = typ._zope_dottedname_style( + '..tests.test_resolver.TestDottedNameResolver', tests + ) + self.assertEqual(result, self.__class__) + + def test__zope_dottedname_style_resolve_relative_is_dot(self): + import tests + + typ = self._makeOne() + result = typ._zope_dottedname_style('.', tests) + self.assertEqual(result, tests) + + def test__zope_dottedname_style_irresolveable_relative_is_dot(self): + typ = self._makeOne() + e = self.config_exc(typ._zope_dottedname_style, '.', None) + 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', None) + self.assertEqual( + e.args[0], + "relative name '.whatever' irresolveable without package", + ) + + def test_zope_dottedname_style_irrresolveable_relative(self): + import tests + + typ = self._makeOne() + self.assertRaises( + ImportError, typ._zope_dottedname_style, '.notexisting', tests + ) + + def test__zope_dottedname_style_resolveable_relative(self): + import tests + + typ = self._makeOne() + result = typ._zope_dottedname_style('.', 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', None + ) + + def test__zope_dottedname_style_resolveable_absolute(self): + typ = self._makeOne() + result = typ._zope_dottedname_style( + 'tests.test_resolver.TestDottedNameResolver', None + ) + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_resolve_absolute(self): + typ = self._makeOne() + result = typ._pkg_resources_style( + 'tests.test_resolver:TestDottedNameResolver', None + ) + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_irrresolveable_absolute(self): + typ = self._makeOne() + self.assertRaises( + ImportError, typ._pkg_resources_style, 'tests:nonexisting', None + ) + + def test__pkg_resources_style_resolve_relative(self): + import tests + + typ = self._makeOne() + result = typ._pkg_resources_style( + '.test_resolver:TestDottedNameResolver', tests + ) + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_resolve_relative_is_dot(self): + import tests + + typ = self._makeOne() + result = typ._pkg_resources_style('.', tests) + self.assertEqual(result, tests) + + def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): + typ = self._makeOne() + self.assertRaises( + ValueError, typ._pkg_resources_style, '.whatever', None + ) + + def test__pkg_resources_style_irrresolveable_relative(self): + import pyramid + + typ = self._makeOne() + self.assertRaises( + ImportError, typ._pkg_resources_style, ':notexisting', pyramid + ) + + 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('tests.test_resolver:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_resolve_using_zope_dottedname_style(self): + typ = self._makeOne() + result = typ.resolve('tests.test_resolver: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_resolve_caller_package(self): + from pyramid.path import CALLER_PACKAGE + + typ = self._makeOne(CALLER_PACKAGE) + self.assertEqual( + typ.resolve('.test_resolver.TestDottedNameResolver'), + self.__class__, + ) + + def test_maybe_resolve_caller_package(self): + from pyramid.path import CALLER_PACKAGE + + typ = self._makeOne(CALLER_PACKAGE) + self.assertEqual( + typ.maybe_resolve('.test_resolver.TestDottedNameResolver'), + self.__class__, + ) + + def test_ctor_string_module_resolveable(self): + import tests + + typ = self._makeOne('tests.test_resolver') + self.assertEqual(typ.package, tests) + + def test_ctor_string_package_resolveable(self): + import tests + + typ = self._makeOne('tests') + self.assertEqual(typ.package, tests) + + def test_ctor_string_irresolveable(self): + self.assertRaises(ValueError, self._makeOne, 'cant.be.found') + + def test_ctor_module(self): + import tests + + from . import test_resolver + + typ = self._makeOne(test_resolver) + self.assertEqual(typ.package, tests) + + def test_ctor_package(self): + import tests + + typ = self._makeOne(tests) + self.assertEqual(typ.package, tests) + + def test_ctor_None(self): + typ = self._makeOne(None) + self.assertEqual(typ.package, None)