Coverage for documents/plugins/date_parsing/__init__.py: 35%

43 statements  

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

1import logging 

2from functools import lru_cache 

3from importlib.metadata import EntryPoint 

4from importlib.metadata import entry_points 

5from typing import Final 

6 

7from django.conf import settings 

8from django.utils import timezone 

9 

10from documents.plugins.date_parsing.base import DateParserConfig 

11from documents.plugins.date_parsing.base import DateParserPluginBase 

12from documents.plugins.date_parsing.regex_parser import RegexDateParserPlugin 

13from paperless.config import OcrConfig 

14from paperless.utils import ocr_to_dateparser_languages 

15 

16logger = logging.getLogger(__name__) 

17 

18DATE_PARSER_ENTRY_POINT_GROUP: Final = "paperless_ngx.date_parsers" 

19 

20 

21@lru_cache(maxsize=1) 

22def _discover_parser_class() -> type[DateParserPluginBase]: 

23 """ 

24 Discovers the date parser plugin class to use. 

25 

26 - If one or more plugins are found, sorts them by name and returns the first. 

27 - If no plugins are found, returns the default RegexDateParser. 

28 """ 

29 

30 eps: tuple[EntryPoint, ...] 

31 try: 

32 eps = entry_points(group=DATE_PARSER_ENTRY_POINT_GROUP) 

33 except Exception as e: 

34 # Log a warning 

35 logger.warning(f"Could not query entry points for date parsers: {e}") 

36 eps = () 

37 

38 valid_plugins: list[EntryPoint] = [] 

39 for ep in eps: 

40 try: 

41 plugin_class = ep.load() 

42 if plugin_class and issubclass(plugin_class, DateParserPluginBase): 

43 valid_plugins.append(ep) 

44 else: 

45 logger.warning(f"Plugin {ep.name} does not subclass DateParser.") 

46 except Exception as e: 

47 logger.exception(f"Unable to load date parser plugin {ep.name}: {e}") 

48 

49 if not valid_plugins: 

50 return RegexDateParserPlugin 

51 

52 valid_plugins.sort(key=lambda ep: ep.name) 

53 

54 if len(valid_plugins) > 1: 

55 logger.warning( 

56 f"Multiple date parsers found: " 

57 f"{[ep.name for ep in valid_plugins]}. " 

58 f"Using the first one by name: '{valid_plugins[0].name}'.", 

59 ) 

60 

61 return valid_plugins[0].load() 

62 

63 

64def get_date_parser() -> DateParserPluginBase: 

65 """ 

66 Factory function to get an initialized date parser instance. 

67 

68 This function is responsible for: 

69 1. Discovering the correct parser class (plugin or default). 

70 2. Loading configuration from Django settings. 

71 3. Instantiating the parser with the configuration. 

72 """ 

73 # 1. Discover the class (this is cached) 

74 parser_class = _discover_parser_class() 

75 

76 # 2. Load configuration from settings 

77 # TODO: Get the language from the settings and/or configuration object, depending 

78 ocr_config = OcrConfig() 

79 languages = settings.DATE_PARSER_LANGUAGES or ocr_to_dateparser_languages( 

80 ocr_config.language, 

81 ) 

82 

83 config = DateParserConfig( 

84 languages=languages, 

85 timezone_str=settings.TIME_ZONE, 

86 ignore_dates=settings.IGNORE_DATES, 

87 reference_time=timezone.now(), 

88 filename_date_order=settings.FILENAME_DATE_ORDER, 

89 content_date_order=settings.DATE_ORDER, 

90 ) 

91 

92 # 3. Instantiate the discovered class with the config 

93 return parser_class(config=config) 

94 

95 

96__all__ = [ 

97 "DateParserConfig", 

98 "DateParserPluginBase", 

99 "RegexDateParserPlugin", 

100 "get_date_parser", 

101]