Coverage for conf/settings/link_validation.py: 76%

33 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 06:14 +0000

1import json 

2import os 

3from collections import defaultdict 

4from datetime import timedelta 

5 

6from django.core.exceptions import ImproperlyConfigured 

7 

8import structlog 

9from decouple import config 

10 

11 

12logger = structlog.get_logger(__name__) 

13 

14 

15LINK_VALIDATION_TIMEOUT_SECONDS = config( 

16 "LINK_VALIDATION_TIMEOUT_SECONDS", default=0.8, cast=float 

17) 

18 

19 

20class LinkValidationCacheExpiryConfiguration(defaultdict): 

21 """Link validation cache expiry configuration.""" 

22 

23 SETTING_PREFIX = "LINK_VALIDATION_CACHE_EXPIRY__" 

24 

25 # Cache successful links for a month, and broken links for 120 days. 

26 OVERALL_DEFAULT = {"days": 120} 

27 STATUS_DEFAULTS = { 

28 200: {"days": 30}, 

29 -1: {"minutes": 30}, 

30 } 

31 

32 def __init__(self): 

33 default = self._config("default", default=self.OVERALL_DEFAULT) 

34 super().__init__(lambda: default) 

35 

36 self.update( 

37 {k: self._config(k, default=v) for k, v in self.STATUS_DEFAULTS.items()} 

38 ) 

39 

40 for k, v in os.environ.items(): 

41 if not k.startswith(self.SETTING_PREFIX) or k.lower().endswith("default"): 41 ↛ 44line 41 didn't jump to line 44 because the condition on line 41 was always true

42 continue 

43 

44 try: 

45 status = int(k.replace(self.SETTING_PREFIX, "")) 

46 except ValueError: 

47 raise ImproperlyConfigured( 

48 "Invalid link validation cache setting name: " 

49 f"{self.SETTING_PREFIX}. Please ensure settings " 

50 "are named in the format of " 

51 f"'{self.SETTING_PREFIX}<http integer status code>'." 

52 ) 

53 

54 value = self._config(status) 

55 

56 self[status] = value 

57 

58 def _config(self, key: str | int, default: dict | None = None) -> int | None: 

59 try: 

60 v = config( 

61 f"{self.SETTING_PREFIX}{str(key)}", 

62 default=default, 

63 # Value should either be a str or dict here 

64 cast=lambda x: json.loads(x) if isinstance(x, str) else x, 

65 ) 

66 return int(timedelta(**v).total_seconds()) 

67 except (json.JSONDecodeError, TypeError): 

68 raise ImproperlyConfigured( 

69 f"Invalid link validation cache setting. Impossible to parse {key}." 

70 ) 

71 

72 

73# Custom link validation expiration times 

74# Overrides can be set via LINK_VALIDATION_CACHE_EXPIRY__<http integer status code> 

75# and should be set as kwarg dicts for datetime.timedelta 

76# E.g. LINK_VALIDATION_CACHE_EXPIRY__200='{"days": 1}' will set the expiration time 

77# for links with HTTP status 200 to 1 day 

78LINK_VALIDATION_CACHE_EXPIRY_CONFIGURATION = LinkValidationCacheExpiryConfiguration()