Created
June 28, 2020 15:53
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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