Skip to main content

A simple base resource class for Pyramid traversal.

Project description

pyramid-resource

Pyramid's URL traversal is a powerful tool and personally one of my favorite features of the framework. Unfortunately, Pyramid doesn't provide any framework or utilities for implementing resource trees. This project aims to reduce the boilerplate necessary for creating feature-full resource trees.

Basic usage

First, of course, you need to add pyramid-resource to your project using your package manager of choice. e.g.: pip install pyramid-resource

Make sure you're familiar with Pyramid's URL traversal.

You can create a new resource by subclassing pyramid_resource.Resource. For example, here's a simple application that has a resource tree with only a root resource.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid_resource import Resource


class Root(Resource):
    pass


def hello_world(request):
    return Response('Hello!\n')


if __name__ == '__main__':
    with Configurator() as config:
        config.set_root_factory(Root)
        config.add_view(hello_world, context=Root)
        app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

You can define child resources by setting the __children__ property to a dictionary. The key corresponds the URL segment and the value should be a resource subclass. pyramid-resource will automatically make the resources location-aware.

class Child(Resource):
    pass


class Root(Resource):
    __children__ = {
        'child': Child,
    }

You can see the full example here.

Name Resolution

For convenience, you can reference children with dotted Python names. This is most useful for referencing child resources that may be defined further down the document. If you use this functionality, you must run Configurator.scan() to trigger the resolution.

class Root(Resource):
    __children__ = {
        'child': '.Child',
    }


class Child(Resource):
    pass

Dynamic resource trees

One of the more interesting features of URL traversal is that trees can be created on the fly. This allows for dynamic resource trees that can mirror the application state, such as objects in a database.

Dynamic resource trees can be created by implementing a get_child method on a resource class. This method should accept a single argument of a URL segment and will be called if no child is found in the __children__ property. If the URL segment corresponds to a valid child resource, get_child should return a resource class and the child resource will be instanciated from that. If no corresponding child is found, None should be returned or KeyError raised, and traversal will be halted.

class Root(Resource):
    def get_child(self, key):
        if exists_in_db(key):
            return Child
        else:
            return None


class Child(Resource):
    pass

Of course, this isn't particularly useful if you can't attach information to the child resource. get_child can also return a two-tuple of a resource class and a dictionary of attributes that will be attached to the resulting child.

class Root(Resource):
    def get_child(self, key):
        if exists_in_db(key):
            return Child, {'id': key}


class Child(Resource):
    pass

The object ID will now be accessible via context.id in views on the child resource. Resources will proxy the attributes of their parent, so context.id will also be accessible in views further down the tree.

If you need to access the current request in your get_child implementations, it's available via self.request.

An example

Here's an example that demonstrates how a real application might utilize pyramid-resource.

from wsgiref.simple_server import make_server
from pyramid.decorator import reify
from pyramid.config import Configurator
from pyramid.view import view_config
from pyramid_resource import Resource


class Root(Resource):
    __children__ = {
        'widget': '.WidgetContainer',
    }


class WidgetContainer(Resource):
    """
    A resource containing the Widget resources.

    """
    def get_child(self, key):
        """
        Return a child resource if the widget exists in the database.

        """
        try:
            id = int(key)
        except ValueError:
            raise KeyError

        if self.request.widget_db.exists(id):
            return Widget, {'widget_id': id}


class Widget(Resource):
    """
    A resource representing a widget in the mock database.

    """
    @reify
    def widget(self):
        """
        Lookup the widget from the database.

        """
        return self.request.widget_db.find(self.widget_id)


@view_config(context=WidgetContainer, renderer='string')
def list_widgets(context, request):
    """
    GET /widget/

    List the URLs of all widgets.

    """
    urls = []
    for widget_id in request.widget_db:
        urls.append(request.resource_path(context[widget_id]))
    return '\n'.join(urls) + '\n'


@view_config(context=Widget, renderer='string')
def get_widget(context, request):
    """
    GET /widget/{id}/

    Greet the current widget.

    """
    return 'Hello {}!\n'.format(context.widget)


class MockDatabase:
    """
    An imitation of a widget database.

    """
    DATA = {
        1: 'Widget 1',
        2: 'Widget 2',
    }

    def exists(self, id):
        return id in self.DATA

    def find(self, id):
        return self.DATA[id]

    def __iter__(self):
        return iter(self.DATA.keys())


if __name__ == '__main__':
    with Configurator() as config:
        config.set_root_factory(Root)
        config.add_request_method(
            lambda _: MockDatabase(),
            'widget_db',
            property=True,
        )
        config.scan()
        app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

The resulting application will behave like this:

>>> curl localhost:8080/widget/
> /widget/1/
> /widget/2/

>>> curl localhost:8080/widget/1/
> Hello Widget 1!

>>> curl localhost:8080/widget/2/
> Hello Widget 2!

Hacking

Developing against pyramid-resource is simple, thanks to Poetry:

  • Install Poetry if you haven't done so already
  • Clone the repository
  • Run poetry install
  • Run the test suite with make test

Prior art

The pyramid_traversalwrapper project proxies a location-ignorant resource tree to make it location-aware.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyramid-resource-0.3.0.tar.gz (5.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyramid_resource-0.3.0-py3-none-any.whl (5.4 kB view details)

Uploaded Python 3

File details

Details for the file pyramid-resource-0.3.0.tar.gz.

File metadata

  • Download URL: pyramid-resource-0.3.0.tar.gz
  • Upload date:
  • Size: 5.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/0.12.17 CPython/3.7.1 Darwin/18.7.0

File hashes

Hashes for pyramid-resource-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ce44e32922f3c832185b4e67a24cf381610baf40cbe5336bd4d522812bf1a557
MD5 9233b745a1447f121038265dacf375c7
BLAKE2b-256 1c67702ebfa5e97fb2e59ce1fd1d830f92737f49422ac4567131213101ad28fe

See more details on using hashes here.

File details

Details for the file pyramid_resource-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: pyramid_resource-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/0.12.17 CPython/3.7.1 Darwin/18.7.0

File hashes

Hashes for pyramid_resource-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8df5c91a26d18ec311e30033918ac0f813d388744edd4d92675d5dc362034720
MD5 e8d4180256b21eb0dc18ee161787d1dc
BLAKE2b-256 38210b804bfa820446d2c010a32e31a6006af8e7492e281d82a3758c451c9382

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page