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
« 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
7from django.conf import settings
8from django.utils import timezone
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
16logger = logging.getLogger(__name__)
18DATE_PARSER_ENTRY_POINT_GROUP: Final = "paperless_ngx.date_parsers"
21@lru_cache(maxsize=1)
22def _discover_parser_class() -> type[DateParserPluginBase]:
23 """
24 Discovers the date parser plugin class to use.
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 """
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 = ()
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}")
49 if not valid_plugins:
50 return RegexDateParserPlugin
52 valid_plugins.sort(key=lambda ep: ep.name)
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 )
61 return valid_plugins[0].load()
64def get_date_parser() -> DateParserPluginBase:
65 """
66 Factory function to get an initialized date parser instance.
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()
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 )
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 )
92 # 3. Instantiate the discovered class with the config
93 return parser_class(config=config)
96__all__ = [
97 "DateParserConfig",
98 "DateParserPluginBase",
99 "RegexDateParserPlugin",
100 "get_date_parser",
101]