Coverage for seedboxsync/__init__.py: 98%

47 statements  

« 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 

25 

26__version__ = version 

27 

28 

29def __handle_http_exception( 

30 e: Exception, 

31) -> tuple[Response, int | None] | tuple[str, int | None]: 

32 """ 

33 Global 404 handler. 

34 

35 Args: 

36 e (Exception): Exception raised while processing the request. 

37 

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) 

44 

45 

46def create_app(test_config: dict[str, str] | None = None) -> Flask: 

47 """ 

48 Create and configure the SeedboxSync Flask application. 

49 

50 Args: 

51 test_config (dict[str, str] | None): Optional configuration overrides 

52 used by tests. 

53 

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 ) 

64 

65 # Configure logger for Flask and Click 

66 logger.configure_logger(app.logger) 

67 

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 

71 

72 # Initialize the database 

73 Database(app) 

74 

75 # Load config 

76 Config(app) 

77 

78 # Initialize Babel 

79 app.config["BABEL_TRANSLATION_DIRECTORIES"] = "front/translations" 

80 babel.init_app(app, locale_selector=get_locale) 

81 

82 # Initialize humanize for each request 

83 @app.before_request 

84 def init_once() -> None: # pyright: ignore [reportUnusedFunction] 

85 humanize_i18n.activate(get_locale()) 

86 

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} 

91 

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 } 

102 

103 # Initialize the cache 

104 cache.init_app(app) 

105 

106 # Register jinja filter 

107 app.jinja_env.filters["slugify"] = slugify 

108 

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] 

113 

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") 

118 

119 return app