Coverage for src / local_deep_research / notifications / url_builder.py: 89%
27 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2URL builder utility for notification callback links.
4This module builds HTTP/HTTPS URLs that are included in notifications for users
5to click on (e.g., https://myapp.com/research/123). This is separate from
6Apprise service URLs which use their own protocols (discord://, mailto://, etc.).
8Uses the existing security URL validator for consistency with the rest of the application.
9"""
11from typing import Optional
12from loguru import logger
14from ..security.url_builder import (
15 build_base_url_from_settings,
16 build_full_url,
17)
18from ..security.url_validator import URLValidator, URLValidationError
21def build_notification_url(
22 path: str,
23 settings_manager=None,
24 settings_snapshot: Optional[dict] = None,
25 fallback_base: str = "http://localhost:5000",
26 validate: bool = True,
27) -> str:
28 """
29 Build a full HTTP/HTTPS callback URL for use in notifications.
31 This builds URLs that users can click on from notifications to return to
32 the application (e.g., https://myapp.com/research/123). This is separate
33 from Apprise service URLs which use their own protocols.
35 Args:
36 path: Relative path (e.g., "/research/123")
37 settings_manager: Optional SettingsManager instance to get external_url
38 settings_snapshot: Optional settings snapshot dict (alternative to settings_manager)
39 fallback_base: Fallback base URL if no setting configured
40 validate: Whether to validate the constructed URL (default: True)
42 Returns:
43 Full HTTP/HTTPS URL (e.g., "https://myapp.com/research/123")
45 Raises:
46 URLValidationError: If validate=True and URL is invalid
48 Note:
49 You can provide either settings_manager OR settings_snapshot, not both.
50 settings_snapshot is preferred for thread-safe background tasks.
51 """
52 try:
53 # Extract settings from snapshot or manager
54 external_url = None
55 host = None
56 port = None
58 if settings_snapshot: 58 ↛ 59line 58 didn't jump to line 59 because the condition on line 58 was never true
59 external_url = settings_snapshot.get("app.external_url", "")
60 host = settings_snapshot.get("app.host", "localhost")
61 port = settings_snapshot.get("app.port", 5000)
62 elif settings_manager:
63 external_url = settings_manager.get_setting(
64 "app.external_url", default=""
65 )
66 host = settings_manager.get_setting("app.host", default="localhost")
67 port = settings_manager.get_setting("app.port", default=5000)
69 # Build base URL using the centralized utility
70 base_url = build_base_url_from_settings(
71 external_url=external_url,
72 host=host,
73 port=port,
74 fallback_base=fallback_base,
75 )
77 # Build full URL
78 full_url = build_full_url(
79 base_url=base_url,
80 path=path,
81 validate=False, # We'll validate with our own validator
82 )
84 # Validate if requested using the security module validator
85 if validate:
86 URLValidator.validate_http_url(full_url)
88 return full_url
90 except Exception as e:
91 if isinstance(e, URLValidationError):
92 raise
94 logger.exception(
95 f"Failed to build notification URL for path '{path}': {e}"
96 )
97 raise URLValidationError(f"Failed to build notification URL: {e}")