view MoinMoin/storage/middleware/routing.py @ 1983:bb2f526d961c

fix some stuff discovered by pycharm code inspection some changes fix yet undiscovered bugs (e.g. due to wrong names), other changes are rather cosmetic or improve docstrings.
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 11 Feb 2013 18:48:03 +0100
parents 384555088cab
children 36d51bad06b3
line wrap: on
line source
# Copyright: 2011 MoinMoin:ThomasWaldmann
# Copyright: 2011 MoinMoin:RonnyPfannschmidt
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.

"""
MoinMoin - namespaces middleware

Routes requests to different backends depending on the namespace.
"""


from __future__ import absolute_import, division

from MoinMoin.constants.keys import NAME, BACKENDNAME, NAMESPACE

from MoinMoin.storage.backends import BackendBase, MutableBackendBase


class Backend(MutableBackendBase):
    """
    namespace dispatcher, behaves readonly for readonly mounts
    """
    def __init__(self, namespaces, backends):
        """
        Initialize.

        The namespace mapping given must satisfy the following criteria:
            * Order matters.
            * Namespaces are unicode strings like u'' (default ns), u'userprofiles:'
              (used to store userprofiles) or u'files:' (could map to a fileserver
              backend). Can be also a hierarchic ns spec like u'foo:bar:'.
            * There *must* be a default namespace entry for u'' at the end of
              the list.

        namespaces = [
            (u'userprofiles:', 'user_be'),
            (u'', 'default_be'), # default (u'') must be last
        ]

        The backends mapping maps backend names to backend instances:

        backends = {
            'default_be': BackendInstance1,
            'user_be': BackendInstance2,
        }

        :type namespaces: list of tuples of namespace specifier -> backend names
        :param namespaces: [(namespace, backend_name), ...]
        :type backends: dict backend names -> backends
        :param backends: {backend_name: backend, ...}
        """
        self.namespaces = namespaces
        self.backends = backends

    def open(self):
        for backend in self.backends.values():
            backend.open()

    def close(self):
        for backend in self.backends.values():
            backend.close()

    def _get_backend(self, fq_names):
        """
        For a given fully-qualified itemname (i.e. something like ns:itemname)
        find the backend it belongs to, the itemname without namespace
        spec and the namespace of the backend.

        :param fq_names: fully-qualified itemnames
        :returns: tuple of (backend name, local item name, namespace)
        """
        fq_name = fq_names[0]
        for namespace, backend_name in self.namespaces:
            if fq_name.startswith(namespace):
                item_names = [fq_name[len(namespace):] for fq_name in fq_names]
                return backend_name, item_names, namespace.rstrip(':')
        raise AssertionError("No backend found for {0!r}. Namespaces: {1!r}".format(fq_name, self.namespaces))

    def __iter__(self):
        # Note: yields enough information so we can retrieve the revision from
        #       the right backend later (this is more than just the revid).
        for backend_name, backend in self.backends.items():
            for revid in backend:  # TODO maybe directly yield the backend?
                yield (backend_name, revid)

    def retrieve(self, backend_name, revid):
        backend = self.backends[backend_name]
        meta, data = backend.retrieve(revid)
        return meta, data

    # writing part
    def create(self):
        for backend in self.backends.values():
            if isinstance(backend, MutableBackendBase):
                backend.create()

    def destroy(self):
        for backend in self.backends.values():
            if isinstance(backend, MutableBackendBase):
                backend.destroy()

    def store(self, meta, data):
        namespace = meta.get(NAMESPACE)
        if namespace is None:
            # if there is no NAMESPACE in metadata, we assume that the NAME
            # is fully qualified and determine the namespace from it:
            fq_names = meta[NAME]
            assert isinstance(fq_names, list)
            if fq_names:
                backend_name, item_names, namespace = self._get_backend(fq_names)
                # side effect: update the metadata with namespace and short item name (no ns)
                meta[NAMESPACE] = namespace
                meta[NAME] = item_names
            else:
                raise ValueError('can not determine namespace: empty NAME list, no NAMESPACE metadata present')
        else:
            if namespace:
                namespace += u':'  # needed for _get_backend
            backend_name, _, _ = self._get_backend([namespace])
        backend = self.backends[backend_name]

        if not isinstance(backend, MutableBackendBase):
            raise TypeError('backend {0} is readonly!'.format(backend_name))

        revid = backend.store(meta, data)

        # add the BACKENDNAME after storing, so it gets only into
        # the index, but not in stored metadata:
        meta[BACKENDNAME] = backend_name
        return backend_name, revid

    def remove(self, backend_name, revid):
        backend = self.backends[backend_name]
        if not isinstance(backend, MutableBackendBase):
            raise TypeError('backend {0} is readonly'.format(backend_name))
        backend.remove(revid)