Coverage for documents/plugins/date_parsing/base.py: 62%

30 statements  

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

1import datetime 

2import logging 

3from abc import ABC 

4from abc import abstractmethod 

5from collections.abc import Iterator 

6from dataclasses import dataclass 

7from types import TracebackType 

8from typing import Self 

9 

10import dateparser 

11 

12logger = logging.getLogger(__name__) 

13 

14 

15@dataclass(frozen=True, slots=True) 

16class DateParserConfig: 

17 """ 

18 Configuration for a DateParser instance. 

19 

20 This object is created by the factory and passed to the 

21 parser's constructor, decoupling the parser from settings. 

22 """ 

23 

24 languages: list[str] 

25 timezone_str: str 

26 ignore_dates: set[datetime.date] 

27 

28 # A "now" timestamp for filtering future dates. 

29 # Passed in by the factory. 

30 reference_time: datetime.datetime 

31 

32 # Settings for the default RegexDateParser 

33 # Other plugins should use or consider these, but it is not required 

34 filename_date_order: str | None 

35 content_date_order: str 

36 

37 

38class DateParserPluginBase(ABC): 

39 """ 

40 Abstract base class for date parsing strategies. 

41 

42 Instances are configured via a DateParserConfig object. 

43 """ 

44 

45 def __init__(self, config: DateParserConfig): 

46 """ 

47 Initializes the parser with its configuration. 

48 """ 

49 self.config = config 

50 

51 def __enter__(self) -> Self: 

52 """ 

53 Enter the runtime context related to this object. 

54 

55 Subclasses can override this to acquire resources (connections, handles). 

56 """ 

57 return self 

58 

59 def __exit__( 

60 self, 

61 exc_type: type[BaseException] | None, 

62 exc_val: BaseException | None, 

63 exc_tb: TracebackType | None, 

64 ) -> None: 

65 """ 

66 Exit the runtime context related to this object. 

67 

68 Subclasses can override this to release resources. 

69 """ 

70 # Default implementation does nothing. 

71 # Returning None implies exceptions are propagated. 

72 

73 def _parse_string( 

74 self, 

75 date_string: str, 

76 date_order: str, 

77 ) -> datetime.datetime | None: 

78 """ 

79 Helper method to parse a single date string using dateparser. 

80 

81 Uses configuration from `self.config`. 

82 """ 

83 try: 

84 return dateparser.parse( 

85 date_string, 

86 settings={ 

87 "DATE_ORDER": date_order, 

88 "PREFER_DAY_OF_MONTH": "first", 

89 "RETURN_AS_TIMEZONE_AWARE": True, 

90 "TIMEZONE": self.config.timezone_str, 

91 }, 

92 locales=self.config.languages, 

93 ) 

94 except Exception as e: 

95 logger.exception(f"Error while parsing date string '{date_string}': {e}") 

96 return None 

97 

98 def _filter_date( 

99 self, 

100 date: datetime.datetime | None, 

101 ) -> datetime.datetime | None: 

102 """ 

103 Helper method to validate a parsed datetime object. 

104 

105 Uses configuration from `self.config`. 

106 """ 

107 if ( 

108 date is not None 

109 and date.year > 1900 

110 and date <= self.config.reference_time 

111 and date.date() not in self.config.ignore_dates 

112 ): 

113 return date 

114 return None 

115 

116 @abstractmethod 

117 def parse(self, filename: str, content: str) -> Iterator[datetime.datetime]: 

118 """ 

119 Parses a document's filename and content, yielding valid datetime objects. 

120 """