Skip to content

Instantly share code, notes, and snippets.

@mmerickel
Created June 28, 2020 15:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmerickel/70308f2cf430a722a843cd046809f91a to your computer and use it in GitHub Desktop.
Save mmerickel/70308f2cf430a722a843cd046809f91a to your computer and use it in GitHub Desktop.
Support annotating resource objects with a URL and using them for view lookup.
from pyramid.interfaces import IResourceURL
from pyramid.traversal import traversal_path
import venusian
from zope.interface import implementedBy, providedBy
from zope.interface.interfaces import IInterface
class resource_config(object):
"""
A class decorator for defining resources.
The decorator should be applied to a factory which accepts the
``request`` and ``vars`` objects. This factory is expected to return
a resource.
.. code-block: python
@resource_config('/users/{user_id}')
class UserResource(object):
def __init__(self, request, vars):
self.user_id = vars['user_id']
def __resource_vars__(self, request):
return {'user_id': self.user_id}
If the resource needs to be separated from the factory
then the resource should be specified as a keyword argument.
.. code-block: python
class UserResource(object):
def __init__(self, user_id):
self.user_id = user_id
def __resource_vars__(self, request):
return {'user_id': self.user_id}
@resource_config(
'/users/{user_id}',
resource='myapp.interfaces.IUserResource',
)
def user_resource_factory(request, vars):
return UserResource(vars['user_id'])
"""
def __init__(self, pattern, **kwargs):
self.pattern = pattern
self.kwargs = kwargs
def __call__(self, wrapped):
kwargs = self.kwargs.copy()
depth = kwargs.pop('_depth', 0)
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_resource(self.pattern, wrapped, **kwargs)
info = venusian.attach(wrapped, callback, category='pyramid',
depth=depth + 1)
return wrapped
def add_resource(config,
pattern,
factory,
resource=None,
**kwargs):
"""
Register a new resource located at a particular path.
:param pattern:
The URL path pattern where this resource will be mounted.
:param factory:
A callable or a :term:`dotted Python name` referring to an object
that accepts ``request`` and ``vars``, returning an object conforming
to the ``resource`` type or interface.
:param resource:
An object or a :term:`dotted Python name` referring to an interface
or class object that will be used as the context for
:term:`view lookup`.
:param kwargs:
Any extra arguments will be passed to
:meth:`pyramid.config.Configurator.add_route`.
"""
factory = config.maybe_dotted(factory)
if resource is not None:
resource = config.maybe_dotted(resource)
else:
resource = factory
# for now just use the pattern as the name
route_name = pattern
kwargs['use_global_views'] = True
kwargs['factory'] = lambda request: factory(request, request.matchdict)
config.add_route(route_name, pattern, **kwargs)
adapter = ResourceURLAdapter(route_name)
config.add_resource_url_adapter(adapter, resource)
config.action(('resource', resource), None)
class ResourceURLAdapter(object):
def __init__(self, route_name):
self.route_name = route_name
def __call__(self, resource, request):
vars = {}
if hasattr(resource, '__resource_vars__'):
vars.update(resource.__resource_vars__(request))
return ResourceURL(request, self.route_name, vars)
class ResourceURL(object):
def __init__(self, request, route_name, vars):
path = request.route_url(route_name, _app_url='', **vars)
path_tuple = traversal_path(path)
self.virtual_path = path
self.physical_path = path
self.virtual_path_tuple = path_tuple
self.physical_path_tuple = path_tuple
class resource_proxy(object):
"""
A helper for loading a resource by type.
Create a :class:`resource_proxy` object using the resource type
or interface, and the resource's path parameters.
.. code-block:: python
resource = resource_proxy(IUserResource, user_id='1')
url = request.resource_url(resource)
"""
def __init__(self, type_or_iface, **kw):
self.iface = type_or_iface
if not IInterface.providedBy(type_or_iface):
self.iface = implementedBy(type_or_iface)
self.kw = kw
def resource_proxy_url_adapter(resource, request):
adapters = request.registry.adapters
adapter = adapters.lookup(
(resource.iface, providedBy(request)), IResourceURL)
route_name = adapter.route_name
return ResourceURL(request, route_name, resource.kw)
def includeme(config):
config.add_directive('add_resource', add_resource)
config.add_resource_url_adapter(resource_proxy_url_adapter, resource_proxy)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment