Coverage for seedboxsync/__init__.py: 98%
47 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""The SeedboxSync main package."""
8from collections.abc import Callable
9from datetime import datetime
10from pathlib import Path
11from flask import Response, request, send_from_directory
12from flask_babel import format_datetime, get_locale as get_babel_locale
13from humanize import i18n as humanize_i18n
14from slugify import slugify
15from seedboxsync.__version__ import (
16 __api_path_version__ as api_path_version,
17 __api_version__ as api_version,
18 __version__ as version,
19)
20from seedboxsync.core import Config, Database, Flask, logger
21from seedboxsync.front.apis import bp as bp_api, error as error_api
22from seedboxsync.front.babel import babel, get_locale
23from seedboxsync.front.cache import cache
24from seedboxsync.front.views import bp as bp_frontend, error as error_front
26__version__ = version
29def __handle_http_exception(
30 e: Exception,
31) -> tuple[Response, int | None] | tuple[str, int | None]:
32 """
33 Global 404 handler.
35 Args:
36 e (Exception): Exception raised while processing the request.
38 Returns:
39 tuple[Response, int | None] | tuple[str, int | None]: JSON for /api routes, else return frontend template.
40 """
41 if request.path.startswith(f"/api/{api_path_version}") or request.blueprint == "api":
42 return error_api.error(e)
43 return error_front.error(e)
46def create_app(test_config: dict[str, str] | None = None) -> Flask:
47 """
48 Create and configure the SeedboxSync Flask application.
50 Args:
51 test_config (dict[str, str] | None): Optional configuration overrides
52 used by tests.
54 Returns:
55 Flask: Configured application instance.
56 """
57 # Create and configure the app
58 app = Flask(
59 __name__,
60 template_folder="front/templates",
61 static_folder="front/static",
62 instance_relative_config=True,
63 )
65 # Configure logger for Flask and Click
66 logger.configure_logger(app.logger)
68 # Load test config
69 if test_config is not None:
70 app.config.from_mapping(test_config) # load the test config if passed in
72 # Initialize the database
73 Database(app)
75 # Load config
76 Config(app)
78 # Initialize Babel
79 app.config["BABEL_TRANSLATION_DIRECTORIES"] = "front/translations"
80 babel.init_app(app, locale_selector=get_locale)
82 # Initialize humanize for each request
83 @app.before_request
84 def init_once() -> None: # pyright: ignore [reportUnusedFunction]
85 humanize_i18n.activate(get_locale())
87 # Inject Jinja function / variables
88 @app.context_processor
89 def inject_formatters() -> dict[str, Callable[[datetime], str]]: # pyright: ignore [reportUnusedFunction]
90 return {"format_datetime": format_datetime}
92 @app.context_processor
93 def inject_globals() -> dict[str, str]: # pyright: ignore [reportUnusedFunction]
94 locale = get_babel_locale() or app.config.get("BABEL_DEFAULT_LOCALE", "en")
95 theme = app.config.get(Config.CONFIG_NAMESPACE + "WEBUI_THEME", "auto")
96 return {
97 "version": version,
98 "theme": theme,
99 "api_version": api_version,
100 "locale": str(locale).replace("_", "-"),
101 }
103 # Initialize the cache
104 cache.init_app(app)
106 # Register jinja filter
107 app.jinja_env.filters["slugify"] = slugify
109 # Register blueprint and error handler
110 app.register_blueprint(bp_frontend)
111 app.register_blueprint(bp_api)
112 app.register_error_handler(Exception, __handle_http_exception) # type: ignore[arg-type]
114 # Serve the favicon from the static directory
115 @app.route("/favicon.ico")
116 def favicon() -> Response: # pyright: ignore [reportUnusedFunction]
117 return send_from_directory(Path(app.root_path) / "static", "favicon.png", mimetype="image/png")
119 return app