Completed
Push — main ( dc9c2e...80557c )
by Jochen
05:20
created

byceps.util.framework.templating._decorate()   A

Complexity

Conditions 3

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
cc 3
eloc 11
nop 2
dl 0
loc 15
ccs 9
cts 10
cp 0.9
crap 3.009
rs 9.85
c 0
b 0
f 0
1
"""
2
byceps.util.framework.templating
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
Templating utilities
6
7
:Copyright: 2006-2020 Jochen Kupperschmidt
8
:License: Modified BSD, see LICENSE for details.
9
"""
10
11 1
from functools import wraps
12 1
from typing import Callable, Optional
13
14 1
from flask import render_template
15
16
17 1
_TEMPLATE_FILENAME_EXTENSION = '.html'
18
19
20 1
def templated(arg) -> Callable:
21
    """Decorate a callable to wrap its return value in a template and that in
22
    a response object.
23
24
    This decorator expects the decorated callable to return a dictionary of
25
    objects that should be added to the template context, or ``None``.
26
27
    The name of the template to render can be either specified as argument or,
28
    if not present, will be determined by concatenating the callable's module
29
    and function object name (format: 'module_callable').
30
31
    The rendered template string will be wrapped in a ``Response`` object and
32
    returned.
33
    """
34
35 1
    if hasattr(arg, '__call__'):
36 1
        return _decorate(arg)
37
38 1
    def wrapper(f: Callable):
39 1
        return _decorate(f, arg)
40
41 1
    return wrapper
42
43
44 1
def _decorate(f: Callable, template_name: Optional[str] = None) -> Callable:
45 1
    @wraps(f)
46
    def decorated(*args, **kwargs):
47 1
        name = _get_template_name(f, template_name)
48
49 1
        context = f(*args, **kwargs)
50
51 1
        if context is None:
52 1
            context = {}
53 1
        elif not isinstance(context, dict):
54
            return context
55
56 1
        return render_template(name, **context)
57
58 1
    return decorated
59
60
61 1
def _get_template_name(
62
    view_function: Callable, template_name: Optional[str]
63
) -> str:
64 1
    if template_name is None:
65 1
        name = _derive_template_name(view_function)
66
    else:
67 1
        name = template_name
68
69 1
    return name + _TEMPLATE_FILENAME_EXTENSION
70
71
72 1
def _derive_template_name(view_function: Callable) -> str:
73
    """Derive the template name from the view function's module and name."""
74
    # Select segments between `byceps.blueprints.` and `.views`.
75 1
    module_package_name_segments = view_function.__module__.split('.')
76 1
    blueprint_path_segments = module_package_name_segments[2:-1]
77
78 1
    action_name = view_function.__name__
79
80
    return '/'.join(blueprint_path_segments + [action_name])
81