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

1""" 

2URL builder utility for notification callback links. 

3 

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

7 

8Uses the existing security URL validator for consistency with the rest of the application. 

9""" 

10 

11from typing import Optional 

12from loguru import logger 

13 

14from ..security.url_builder import ( 

15 build_base_url_from_settings, 

16 build_full_url, 

17) 

18from ..security.url_validator import URLValidator, URLValidationError 

19 

20 

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. 

30 

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. 

34 

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) 

41 

42 Returns: 

43 Full HTTP/HTTPS URL (e.g., "https://myapp.com/research/123") 

44 

45 Raises: 

46 URLValidationError: If validate=True and URL is invalid 

47 

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 

57 

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) 

68 

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 ) 

76 

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 ) 

83 

84 # Validate if requested using the security module validator 

85 if validate: 

86 URLValidator.validate_http_url(full_url) 

87 

88 return full_url 

89 

90 except Exception as e: 

91 if isinstance(e, URLValidationError): 

92 raise 

93 

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