Source code for flask_copilot

"""Navigation help for Flask applications."""

from copy import deepcopy
from functools import partial, total_ordering
import os
import pkg_resources
import sys

from flask import url_for
from sortedcontainers import SortedList
from werkzeug.routing import Rule

__all__ = ('Copilot',)

try:
    _dist = pkg_resources.get_distribution(__package__)
    if not __file__.startswith(os.path.join(_dist.location, __package__)):
        # Manually raise the exception if there is a distribution but
        # it's installed from elsewhere.
        raise pkg_resources.DistributionNotFound
except pkg_resources.DistributionNotFound:
    __version__ = 'development'
else:
    __version__ = _dist.version


MODERN_PYTHON = sys.version_info.major >= 3
if MODERN_PYTHON:
    basestring = str


[docs]class Copilot(object): """The Flask-Copilot extension. Args: app (Optional[flask.Flask]): The application instance that this copilot should be registered to. """ def __init__(self, app=None): """Initialize the extension.""" self.navbar_entries = SortedList() if app is not None: self.init_app(app)
[docs] def init_app(self, app): """Register the extension with the application. Args: app (flask.Flask): The application to register with. """ app.url_rule_class = partial(NavigationRule, copilot=self) app.context_processor(self.inject_context)
[docs] def inject_context(self): """Return a dict used for a template context.""" navbar = filter(lambda entry: entry.visible, self.navbar_entries) return {'navbar': navbar}
[docs] def register_entry(self, navbar_kwargs): """Register a navbar entry with the copilot. Args: navbar_kwargs (dict): Arguments passed to the :class:`NavbarEntry` instance. """ # Add a new rule for each level in the path. path = navbar_kwargs.pop('path') # If a single object is used rather than an iterable (including # a single string), wrap it before using. if not hasattr(path, '__iter__') or isinstance(path, basestring): path = [path] entry_group = self.navbar_entries # HACK: I'd like to intelligently replace the URL rule in the # case where the intended rule is provided, but the function has # already created a blank "placeholder" rule for it. There are # probably nicer ways to approach this, but it works. for name, is_last in iter_islast(path): kwargs = deepcopy(navbar_kwargs) kwargs['name'] = name for existing_entry in entry_group: # If there's an existing entry for this "link", use it # instead of creating a new one. If this existing entry # has no rule and this is the last item in ``path``, the # rule was intended to be assigned to this entry, so # overwrite the blank rule with the one provided via # ``navbar_kwargs``. if existing_entry.name == name: entry = existing_entry if is_last: entry.endpoint = kwargs['endpoint'] break else: # If we can't find an existing entry, create one with a # blank endpoint. If this rule is not the final one in # the list, the endpoint was not intended for this, so # don't assign it. if not is_last: kwargs['endpoint'] = None entry = NavbarEntry(**kwargs) entry_group.add(entry) entry_group = entry.children
@total_ordering class NavigationRule(Rule): """Store navigation information along with routing rules. Args: *args (list): Positional arguments passed to the underlying `werkzeug.routing.Rule` object. **kwargs (dict): Keyword arguments passed to the underlying :class:`werkzeug.routing.Rule` object. copilot (Copilot): The Flask-Copilot instance that this rule's navbar entry will be registered with. This argument must be passed as a keyword argument. navbar_kwargs (Optional[dict]): Keyword args passed to :class:`NavbarEntry`. This argument must be passed as a keyword argument. """ def __init__(self, *args, **kwargs): """Initialize the rule.""" copilot = kwargs.pop('copilot') navbar_kwargs = kwargs.pop('navbar_kwargs', None) super(NavigationRule, self).__init__(*args, **kwargs) if navbar_kwargs: navbar_kwargs['endpoint'] = self.endpoint copilot.register_entry(navbar_kwargs) # NOTE: Taken and slightly modified from the ActiveState Code Recipe at # https://code.activestate.com/recipes/392015-finding-the-last-item-in-a-loop/ def iter_islast(iterable): """Generate (item, islast) pairs for an iterable. Generates pairs where the first element is an item from the iterable source and the second element is a boolean flag indicating if it is the last item in the sequence. """ it = iter(iterable) prev = next(it) for item in it: yield prev, False prev = item yield prev, True