|
3 | 3 | # This file is part of the Pycroft project and licensed under the terms of
|
4 | 4 | # the Apache License, Version 2.0. See the LICENSE file for details.
|
5 | 5 |
|
6 |
| -import argparse |
7 | 6 | import logging
|
8 | 7 | import os
|
9 | 8 | import sys
|
10 | 9 | import time
|
11 |
| -from collections.abc import Callable |
12 | 10 |
|
13 | 11 | from babel.support import Translations
|
14 | 12 | from flask import g, request
|
15 | 13 | from flask.globals import request_ctx
|
| 14 | +from sqlalchemy import create_engine |
16 | 15 | from sqlalchemy.orm import scoped_session, sessionmaker
|
| 16 | +from sqlalchemy.ext.declarative import DeferredReflection |
17 | 17 | from werkzeug.middleware.profiler import ProfilerMiddleware
|
18 | 18 |
|
19 | 19 | import pycroft
|
20 | 20 | import web
|
21 | 21 | from pycroft.helpers.i18n import set_translation_lookup, get_locale
|
22 | 22 | from pycroft.model.session import set_scoped_session
|
23 |
| -from scripts.connection import try_create_connection, get_connection_string |
| 23 | +from scripts.connection import get_connection_string |
24 | 24 | from scripts.schema import AlembicHelper, SchemaStrategist
|
25 | 25 | from web import make_app, PycroftFlask
|
26 | 26 |
|
|
30 | 30 | )
|
31 | 31 |
|
32 | 32 |
|
33 |
| -def prepare_server(args) -> tuple[PycroftFlask, Callable]: |
34 |
| - """returns both the prepared app and a callback executing `app.run`""" |
35 |
| - if args.echo: |
| 33 | +def prepare_server(echo=False) -> PycroftFlask: |
| 34 | + if echo: |
36 | 35 | logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
37 | 36 | logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
38 | 37 | logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG)
|
39 |
| - app = make_app(args.debug) |
40 |
| - |
41 | 38 | logging.getLogger('pycroft').addHandler(default_handler)
|
42 | 39 |
|
43 |
| - wait_for_db: bool = args.wait_for_database |
44 |
| - |
45 |
| - connection_string = get_connection_string() |
46 |
| - connection, engine = try_create_connection(connection_string, wait_for_db, app.logger, |
47 |
| - reflections=False) |
48 |
| - |
49 |
| - state = AlembicHelper(connection) |
50 |
| - if args.force_schema_create: |
51 |
| - strategy = SchemaStrategist(state).create_then_stamp |
52 |
| - else: |
53 |
| - strategy = SchemaStrategist(state).determine_schema_strategy() |
54 |
| - strategy() |
55 |
| - |
56 |
| - @app.before_request |
57 |
| - def get_time(): |
58 |
| - g.request_time = time.time() |
59 |
| - |
60 |
| - @app.teardown_request |
61 |
| - def time_response(exception=None): |
62 |
| - request_time = g.pop('request_time', None) |
63 |
| - |
64 |
| - if request_time: |
65 |
| - time_taken = time.time() - request_time |
66 |
| - if time_taken > 0.5: |
67 |
| - app.logger.warning( |
68 |
| - "Response took %s seconds for request %s", |
69 |
| - time_taken, request.full_path, |
70 |
| - ) |
71 |
| - |
72 |
| - connection, engine = try_create_connection(connection_string, wait_for_db, app.logger, |
73 |
| - args.profile) |
| 40 | + app = make_app() |
| 41 | + # TODO rename to `default_config.toml` |
| 42 | + app.config.from_pyfile("flask.cfg") |
74 | 43 |
|
| 44 | + engine = create_engine(get_connection_string()) |
| 45 | + with engine.connect() as connection: |
| 46 | + _ensure_schema_up_to_date(connection) |
| 47 | + _setup_simple_profiling(app) |
| 48 | + DeferredReflection.prepare(engine) # TODO for what is this used? |
75 | 49 | set_scoped_session(
|
76 | 50 | scoped_session(
|
77 | 51 | sessionmaker(bind=engine),
|
78 | 52 | scopefunc=lambda: request_ctx._get_current_object(),
|
79 | 53 | )
|
80 | 54 | )
|
| 55 | + _setup_translations() |
| 56 | + if app.config.get("PROFILE", False): |
| 57 | + app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) |
| 58 | + return app |
81 | 59 |
|
| 60 | + |
| 61 | +def _ensure_schema_up_to_date(connection): |
| 62 | + state = AlembicHelper(connection) |
| 63 | + strategy = SchemaStrategist(state).determine_schema_strategy() |
| 64 | + strategy() |
| 65 | + |
| 66 | + |
| 67 | +def _setup_translations(): |
82 | 68 | def lookup_translation():
|
83 | 69 | ctx = request_ctx
|
84 | 70 | if ctx is None:
|
85 | 71 | return None
|
86 |
| - translations = getattr(ctx, 'pycroft_translations', None) |
| 72 | + translations = getattr(ctx, "pycroft_translations", None) |
87 | 73 | if translations is None:
|
88 | 74 | translations = Translations()
|
89 | 75 | for module in (pycroft, web):
|
| 76 | + # TODO this has a bug. The intention is to merge |
| 77 | + # `pycroft.translations` and `web.translations`, |
| 78 | + # but the `os.path.dirname(…)` result is nowhere used. |
90 | 79 | os.path.dirname(module.__file__)
|
91 |
| - dirname = os.path.join(ctx.app.root_path, 'translations') |
| 80 | + dirname = os.path.join(ctx.app.root_path, "translations") |
92 | 81 | translations.merge(Translations.load(dirname, [get_locale()]))
|
93 | 82 | ctx.pycroft_translations = translations
|
94 | 83 | return translations
|
95 | 84 |
|
96 | 85 | set_translation_lookup(lookup_translation)
|
97 |
| - app.config.from_pyfile('flask.cfg') |
98 |
| - if args.profile: |
99 |
| - app.config['PROFILE'] = True |
100 |
| - app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) |
101 |
| - return app, lambda: app.run( |
102 |
| - debug=args.debug, |
103 |
| - port=args.port, |
104 |
| - host=args.host, |
105 |
| - threaded=True, |
106 |
| - exclude_patterns=["**/lib/python3*"], |
107 |
| - ) |
108 | 86 |
|
109 | 87 |
|
110 |
| -def create_parser() -> argparse.ArgumentParser: |
111 |
| - parser = argparse.ArgumentParser(description="Pycroft launcher") |
112 |
| - parser.add_argument("--debug", action="store_true", |
113 |
| - help="run in debug mode") |
114 |
| - parser.add_argument("--echo", action="store_true", |
115 |
| - help="log sqlalchemy actions") |
116 |
| - parser.add_argument("--profile", action="store_true", |
117 |
| - help="profile and log sql queries") |
118 |
| - parser.add_argument("--exposed", action="store_const", const='0.0.0.0', |
119 |
| - dest='host', help="expose server on network") |
120 |
| - parser.add_argument("-p", "--port", action="store", |
121 |
| - help="port to run Pycroft on", type=int, default=5000) |
122 |
| - parser.add_argument("-w", "--wait-for-database", type=int, default=30, |
123 |
| - help="Maximum time to wait for database to become " |
124 |
| - "available. Use 0 to wait forever.") |
125 |
| - parser.add_argument("--force-schema-create", action='store_true') |
126 |
| - return parser |
127 |
| - |
128 |
| - |
129 |
| -app, run_callable = prepare_server(create_parser().parse_args()) |
130 |
| - |
131 |
| -if __name__ == "__main__": |
132 |
| - run_callable() |
| 88 | +def _setup_simple_profiling(app): |
| 89 | + @app.before_request |
| 90 | + def get_time(): |
| 91 | + g.request_time = time.time() |
| 92 | + |
| 93 | + @app.teardown_request |
| 94 | + def time_response(exception=None): |
| 95 | + request_time = g.pop('request_time', None) |
| 96 | + |
| 97 | + if request_time: |
| 98 | + time_taken = time.time() - request_time |
| 99 | + if time_taken > 0.5: |
| 100 | + app.logger.warning( |
| 101 | + "Response took %s seconds for request %s", |
| 102 | + time_taken, request.full_path, |
| 103 | + ) |
0 commit comments