Passed
Push — master ( cdbff3...1bb58d )
by Jochen
02:23
created

byceps/application.py (1 issue)

1
"""
2
byceps.application
3
~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2019 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9
from importlib import import_module
10
from pathlib import Path
11
from typing import Any, Dict, Iterator, Optional, Tuple, Union
12
13
from flask import Flask, redirect
14
import jinja2
15
16
from .blueprints.snippet.init import add_routes_for_snippets
17
from . import config, config_defaults
18
from .database import db
19
from . import email
20
from .redis import redis
21
from .util.framework.blueprint import register_blueprint
22
from .util.l10n import set_locale
23
from .util import templatefilters
24
from .util.templating import SiteTemplateOverridesLoader
25
26
27
BlueprintReg = Tuple[str, Optional[str]]
28
29
30
def create_app(config_filename: Union[Path, str], config_overrides: Optional[Dict[str, Any]]=None) -> Flask:
1 ignored issue
show
This code seems to be duplicated in your project.
Loading history...
31
    """Create the actual Flask application."""
32
    app = Flask(__name__)
33
34
    app.config.from_object(config_defaults)
35
    app.config.from_pyfile(str(config_filename))
36
    if config_overrides is not None:
37
        app.config.from_mapping(config_overrides)
38
39
    # Throw an exception when an undefined name is referenced in a template.
40
    app.jinja_env.undefined = jinja2.StrictUndefined
41
42
    # Set the locale.
43
    set_locale(app.config['LOCALE'])  # Fail if not configured.
44
45
    # Initialize database.
46
    db.init_app(app)
47
48
    # Initialize Redis connection.
49
    redis.init_app(app)
50
51
    email.init_app(app)
52
53
    config.init_app(app)
54
55
    _register_blueprints(app)
56
57
    templatefilters.register(app)
58
59
    _add_static_file_url_rules(app)
60
61
    return app
62
63
64
def _register_blueprints(app: Flask) -> None:
65
    """Register blueprints depending on the configuration."""
66
    for name, url_prefix in _get_blueprints(app):
67
        register_blueprint(app, name, url_prefix)
68
69
70
def _get_blueprints(app: Flask) -> Iterator[BlueprintReg]:
71
    """Yield blueprints to register on the application."""
72
    yield from _get_blueprints_common()
73
74
    current_mode = config.get_site_mode(app)
75
    if current_mode.is_public():
76
        yield from _get_blueprints_site()
77
    elif current_mode.is_admin():
78
        yield from _get_blueprints_admin()
79
80
    yield from _get_blueprints_api()
81
82
    yield from _get_blueprints_health()
83
84
    if app.config['METRICS_ENABLED']:
85
        yield from _get_blueprints_metrics()
86
87
    if app.debug:
88
        yield from _get_blueprints_debug()
89
90
91
def _get_blueprints_common() -> Iterator[BlueprintReg]:
92
    yield from [
93
        ('authentication',              '/authentication'           ),
94
        ('authorization',               None                        ),
95
        ('core',                        '/core'                     ),
96
        ('user',                        None                        ),
97
        ('user.avatar',                 '/users'                    ),
98
        ('user.creation',               '/users'                    ),
99
        ('user.current',                '/users'                    ),
100
        ('user.email_address',          '/users/email_address'      ),
101
    ]
102
103
104
def _get_blueprints_site() -> Iterator[BlueprintReg]:
105
    yield from [
106
        ('attendance',                  '/attendance'               ),
107
        ('board',                       '/board'                    ),
108
        ('consent',                     '/consent'                  ),
109
        ('news',                        '/news'                     ),
110
        ('newsletter',                  '/newsletter'               ),
111
        ('orga_team',                   '/orgas'                    ),
112
        ('party',                       None                        ),
113
        ('seating',                     '/seating'                  ),
114
        ('shop.order',                  '/shop'                     ),
115
        ('shop.orders',                 '/shop/orders'              ),
116
        ('snippet',                     '/snippets'                 ),
117
        ('terms',                       '/terms'                    ),
118
        ('ticketing',                   '/tickets'                  ),
119
        ('user.profile',                '/users'                    ),
120
        ('user_badge',                  '/user_badges'              ),
121
        ('user_group',                  '/user_groups'              ),
122
        ('user_message',                '/user_messages'            ),
123
    ]
124
125
126
def _get_blueprints_admin() -> Iterator[BlueprintReg]:
127
    yield from [
128
        ('admin.attendance',            '/admin/attendance'         ),
129
        ('admin.authorization',         '/admin/authorization'      ),
130
        ('admin.board',                 '/admin/board'              ),
131
        ('admin.brand',                 '/admin/brands'             ),
132
        ('admin.consent',               '/admin/consent'            ),
133
        ('admin.core',                  None                        ),
134
        ('admin.dashboard',             '/admin/dashboard'          ),
135
        ('admin.email',                 '/admin/email'              ),
136
        ('admin.news',                  '/admin/news'               ),
137
        ('admin.newsletter',            '/admin/newsletter'         ),
138
        ('admin.jobs',                  '/admin/jobs'               ),
139
        ('admin.orga',                  '/admin/orgas'              ),
140
        ('admin.orga_presence',         '/admin/presence'           ),
141
        ('admin.orga_team',             '/admin/orga_teams'         ),
142
        ('admin.party',                 '/admin/parties'            ),
143
        ('admin.seating',               '/admin/seating'            ),
144
        ('admin.shop',                  None                        ),
145
        ('admin.shop.article',          '/admin/shop/articles'      ),
146
        ('admin.shop.email',            '/admin/shop/email'         ),
147
        ('admin.shop.order',            '/admin/shop/orders'        ),
148
        ('admin.shop.shipping',         '/admin/shop/shipping'      ),
149
        ('admin.shop.shop',             '/admin/shop/shop'          ),
150
        ('admin.site',                  '/admin/sites'              ),
151
        ('admin.snippet',               '/admin/snippets'           ),
152
        ('admin.terms',                 '/admin/terms'              ),
153
        ('admin.ticketing',             '/admin/ticketing'          ),
154
        ('admin.ticketing.checkin',     '/admin/ticketing/checkin'  ),
155
        ('admin.tourney',               '/admin/tourney'            ),
156
        ('admin.user',                  '/admin/users'              ),
157
        ('admin.user_badge',            '/admin/user_badges'        ),
158
    ]
159
160
161
def _get_blueprints_api() -> Iterator[BlueprintReg]:
162
    yield from [
163
        ('api.attendance',              '/api/attendances'          ),
164
        ('api.snippet',                 '/api/snippets'             ),
165
        ('api.tourney.avatar',          '/api/tourney/avatars'      ),
166
        ('api.tourney.match.comments',  '/api/tourney'              ),
167
        ('api.user',                    '/api/users'                ),
168
        ('api.user_badge',              '/api/user_badges'          ),
169
    ]
170
171
172
def _get_blueprints_health() -> Iterator[BlueprintReg]:
173
    yield from [
174
        ('healthcheck',                 '/health'                   ),
175
    ]
176
177
178
def _get_blueprints_metrics() -> Iterator[BlueprintReg]:
179
    yield from [
180
        ('metrics',                     '/metrics'                  ),
181
    ]
182
183
184
def _get_blueprints_debug() -> Iterator[BlueprintReg]:
185
    yield from [
186
        ('style_guide',                 '/style_guide'              ),
187
    ]
188
189
190
def _add_static_file_url_rules(app: Flask) -> None:
191
    """Add URL rules to for static files."""
192
    for rule_prefix, endpoint in [
193
        ('/global', 'global_file'),
194
        ('/brand', 'brand_file'),
195
        ('/party', 'party_file'),
196
        ('/site', 'site_file'),
197
    ]:
198
        rule = rule_prefix + '/<path:filename>'
199
        app.add_url_rule(
200
            rule, endpoint=endpoint, methods=['GET'], build_only=True
201
        )
202
203
204
def init_app(app: Flask) -> None:
205
    """Initialize the application after is has been created."""
206
    with app.app_context():
207
        _set_url_root_path(app)
208
209
        site_mode = config.get_site_mode()
210
        if site_mode.is_public():
211
            # Incorporate site-specific template overrides.
212
            app.jinja_loader = SiteTemplateOverridesLoader()
213
214
            site_id = config.get_current_site_id()
215
216
            # Mount snippets.
217
            add_routes_for_snippets(site_id)
218
219
            # Import site-specific code.
220
            _load_site_extension(app, site_id)
221
        elif site_mode.is_admin() and app.config['RQ_DASHBOARD_ENABLED']:
222
            import rq_dashboard
223
224
            app.register_blueprint(
225
                rq_dashboard.blueprint, url_prefix='/admin/rq'
226
            )
227
228
229
def _set_url_root_path(app: Flask) -> None:
230
    """Set an optional URL path to redirect to from the root URL path (`/`).
231
232
    Important: Don't specify the target with a leading slash unless you
233
    really mean the root of the host.
234
    """
235
    target_url = app.config['ROOT_REDIRECT_TARGET']
236
    if target_url is None:
237
        return
238
239
    status_code = app.config['ROOT_REDIRECT_STATUS_CODE']
240
241
    def _redirect():
242
        return redirect(target_url, status_code)
243
244
    app.add_url_rule('/', endpoint='root', view_func=_redirect)
245
246
247
def _load_site_extension(app: Flask, site_id: str) -> None:
248
    """Import and call custom extension code from the site's package, if
249
    available.
250
251
    If a site package contains a module named `extension` and it
252
    contains a top-level callable named `template_context_processor`,
253
    then that callable is registered with the application as a template
254
    context processor.
255
    """
256
    module_name = f'sites.{site_id}.extension'
257
    try:
258
        module = import_module(module_name)
259
    except ModuleNotFoundError:
260
        # No extension module found in site package.
261
        return
262
263
    context_processor = getattr(module, 'template_context_processor', None)
264
    if context_processor is None:
265
        # Context processor not found in module.
266
        return
267
268
    if not callable(context_processor):
269
        # Context processor object is not callable.
270
        return
271
272
    app.context_processor(context_processor)
273